From d15820545eecb358286edf83e5c35683de0e3ae4 Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Wed, 20 Nov 2024 00:26:29 -0800 Subject: [PATCH 01/29] Include operation information in the Change struct --- ingest/change.go | 16 +++++-- ingest/ledger_transaction.go | 89 ++++++++++++++++++++++++++++++------ 2 files changed, 87 insertions(+), 18 deletions(-) diff --git a/ingest/change.go b/ingest/change.go index 0a2c063f1c..50dc95f300 100644 --- a/ingest/change.go +++ b/ingest/change.go @@ -16,10 +16,20 @@ import ( // If an entry is created: Pre is nil and Post is not nil. // If an entry is updated: Pre is not nil and Post is not nil. // If an entry is removed: Pre is not nil and Post is nil. +// If this change is caused by a operation in a transaction, include the operation information. Wont work when changes are compacted type Change struct { - Type xdr.LedgerEntryType - Pre *xdr.LedgerEntry - Post *xdr.LedgerEntry + Type xdr.LedgerEntryType + Pre *xdr.LedgerEntry + Post *xdr.LedgerEntry + isOperationChange bool + operationInfo *OperationInfo +} + +type OperationInfo struct { + operationIdx uint32 + operation *xdr.Operation + operationResult *xdr.OperationResultTr + txEnvelope *xdr.TransactionEnvelope } // String returns a best effort string representation of the change. diff --git a/ingest/ledger_transaction.go b/ingest/ledger_transaction.go index 77ca777206..d3c8e3f4f9 100644 --- a/ingest/ledger_transaction.go +++ b/ingest/ledger_transaction.go @@ -42,6 +42,7 @@ func (t *LedgerTransaction) GetChanges() ([]Change, error) { return changes, errors.New("TransactionMeta.V=0 not supported") case 1: v1Meta := t.UnsafeMeta.MustV1() + // The var `txChanges` reflect the ledgerEntryChanges that are changed because of the transaction as a whole txChanges := GetChangesFromLedgerEntryChanges(v1Meta.TxChanges) changes = append(changes, txChanges...) @@ -50,34 +51,54 @@ func (t *LedgerTransaction) GetChanges() ([]Change, error) { return changes, nil } - for _, operationMeta := range v1Meta.Operations { + // These changes reflect the ledgerEntry changes that were caused by the operations in the transaction + // Populate the operationInfo for these changes in the `Change` struct + + // TODO: Refactor this to use LedgerTransaction.GetOperationChanges + for opIdx, operationMeta := range v1Meta.Operations { opChanges := GetChangesFromLedgerEntryChanges( operationMeta.Changes, ) + for _, change := range opChanges { + op, found := t.GetOperation(uint32(opIdx)) + if !found { + return []Change{}, errors.New("could not find operation") + } + results, _ := t.Result.OperationResults() + operationResult := results[opIdx].MustTr() + operationInfo := OperationInfo{ + operationIdx: uint32(opIdx), + operation: &op, + operationResult: &operationResult, + txEnvelope: &t.Envelope, + } + change.operationInfo = &operationInfo + change.isOperationChange = true + } changes = append(changes, opChanges...) } case 2, 3: var ( - beforeChanges, afterChanges xdr.LedgerEntryChanges - operationMeta []xdr.OperationMeta + txBeforeChanges, txAfterChanges xdr.LedgerEntryChanges + operationMeta []xdr.OperationMeta ) switch t.UnsafeMeta.V { case 2: v2Meta := t.UnsafeMeta.MustV2() - beforeChanges = v2Meta.TxChangesBefore - afterChanges = v2Meta.TxChangesAfter + txBeforeChanges = v2Meta.TxChangesBefore + txAfterChanges = v2Meta.TxChangesAfter operationMeta = v2Meta.Operations case 3: v3Meta := t.UnsafeMeta.MustV3() - beforeChanges = v3Meta.TxChangesBefore - afterChanges = v3Meta.TxChangesAfter + txBeforeChanges = v3Meta.TxChangesBefore + txAfterChanges = v3Meta.TxChangesAfter operationMeta = v3Meta.Operations default: panic("Invalid meta version, expected 2 or 3") } - txChangesBefore := GetChangesFromLedgerEntryChanges(beforeChanges) + txChangesBefore := GetChangesFromLedgerEntryChanges(txBeforeChanges) changes = append(changes, txChangesBefore...) // Ignore operations meta and txChangesAfter if txInternalError @@ -86,14 +107,31 @@ func (t *LedgerTransaction) GetChanges() ([]Change, error) { return changes, nil } - for _, operationMeta := range operationMeta { + // TODO: Refactor this to use LedgerTransaction.GetOperationChanges + for opIdx, operationMetaChanges := range operationMeta { opChanges := GetChangesFromLedgerEntryChanges( - operationMeta.Changes, + operationMetaChanges.Changes, ) + for _, change := range opChanges { + op, found := t.GetOperation(uint32(opIdx)) + if !found { + return []Change{}, errors.New("could not find operation") + } + results, _ := t.Result.OperationResults() + operationResult := results[opIdx].MustTr() + operationInfo := OperationInfo{ + operationIdx: uint32(opIdx), + operation: &op, + operationResult: &operationResult, + txEnvelope: &t.Envelope, + } + change.operationInfo = &operationInfo + change.isOperationChange = true + } changes = append(changes, opChanges...) } - txChangesAfter := GetChangesFromLedgerEntryChanges(afterChanges) + txChangesAfter := GetChangesFromLedgerEntryChanges(txAfterChanges) changes = append(changes, txChangesAfter...) default: return changes, errors.New("Unsupported TransactionMeta version") @@ -137,18 +175,39 @@ func (t *LedgerTransaction) GetOperationChanges(operationIndex uint32) ([]Change return changes, errors.New("Unsupported TransactionMeta version") } - return operationChanges(operationMeta, operationIndex), nil + changes, err := t.operationChanges(operationMeta, operationIndex) + if err != nil { + return []Change{}, err + } + return changes, nil } -func operationChanges(ops []xdr.OperationMeta, index uint32) []Change { +func (t *LedgerTransaction) operationChanges(ops []xdr.OperationMeta, index uint32) ([]Change, error) { if int(index) >= len(ops) { - return []Change{} + return []Change{}, errors.New("operation index out of range") } operationMeta := ops[index] - return GetChangesFromLedgerEntryChanges( + changes := GetChangesFromLedgerEntryChanges( operationMeta.Changes, ) + for _, change := range changes { + op, found := t.GetOperation(index) + if !found { + return []Change{}, errors.New("could not find operation") + } + results, _ := t.Result.OperationResults() + operationResult := results[index].MustTr() + operationInfo := OperationInfo{ + operationIdx: index, + operation: &op, + operationResult: &operationResult, + txEnvelope: &t.Envelope, + } + change.operationInfo = &operationInfo + change.isOperationChange = true + } + return changes, nil } // GetDiagnosticEvents returns all contract events emitted by a given operation. From 66abef57b37be1686cf83720424592d34672a521 Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Wed, 20 Nov 2024 00:55:50 -0800 Subject: [PATCH 02/29] Dont error out when operation index out of range --- ingest/ledger_transaction.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ingest/ledger_transaction.go b/ingest/ledger_transaction.go index d3c8e3f4f9..e2def262fd 100644 --- a/ingest/ledger_transaction.go +++ b/ingest/ledger_transaction.go @@ -184,7 +184,7 @@ func (t *LedgerTransaction) GetOperationChanges(operationIndex uint32) ([]Change func (t *LedgerTransaction) operationChanges(ops []xdr.OperationMeta, index uint32) ([]Change, error) { if int(index) >= len(ops) { - return []Change{}, errors.New("operation index out of range") + return []Change{}, nil // TODO - operations_processor somehow seems to be failing without this } operationMeta := ops[index] From fb94fdff2748c2743493a79b8aecc328c08ebf98 Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Wed, 20 Nov 2024 19:29:21 -0800 Subject: [PATCH 03/29] Fix failing unit test --- ingest/ledger_transaction.go | 6 +++--- xdr/transaction_envelope.go | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ingest/ledger_transaction.go b/ingest/ledger_transaction.go index e2def262fd..d6bf544471 100644 --- a/ingest/ledger_transaction.go +++ b/ingest/ledger_transaction.go @@ -62,7 +62,7 @@ func (t *LedgerTransaction) GetChanges() ([]Change, error) { for _, change := range opChanges { op, found := t.GetOperation(uint32(opIdx)) if !found { - return []Change{}, errors.New("could not find operation") + continue } results, _ := t.Result.OperationResults() operationResult := results[opIdx].MustTr() @@ -115,7 +115,7 @@ func (t *LedgerTransaction) GetChanges() ([]Change, error) { for _, change := range opChanges { op, found := t.GetOperation(uint32(opIdx)) if !found { - return []Change{}, errors.New("could not find operation") + continue } results, _ := t.Result.OperationResults() operationResult := results[opIdx].MustTr() @@ -194,7 +194,7 @@ func (t *LedgerTransaction) operationChanges(ops []xdr.OperationMeta, index uint for _, change := range changes { op, found := t.GetOperation(index) if !found { - return []Change{}, errors.New("could not find operation") + continue } results, _ := t.Result.OperationResults() operationResult := results[index].MustTr() diff --git a/xdr/transaction_envelope.go b/xdr/transaction_envelope.go index 6d513ff342..b2259fedea 100644 --- a/xdr/transaction_envelope.go +++ b/xdr/transaction_envelope.go @@ -215,6 +215,10 @@ func (e TransactionEnvelope) Preconditions() Preconditions { // Note for fee bump transactions, Operations() returns the operations // of the inner transaction func (e TransactionEnvelope) Operations() []Operation { + // This is not expected to happen. + if (e == TransactionEnvelope{}) { + return []Operation{} + } switch e.Type { case EnvelopeTypeEnvelopeTypeTxFeeBump: return e.FeeBump.Tx.InnerTx.V1.Tx.Operations From 06139d3c02079545097724a4bb3fbbd61a501e16 Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Wed, 20 Nov 2024 22:59:13 -0800 Subject: [PATCH 04/29] Reafactor ledger_transaction.GetChanges() to use internal functions --- ingest/ledger_transaction.go | 71 ++++++++---------------------------- 1 file changed, 15 insertions(+), 56 deletions(-) diff --git a/ingest/ledger_transaction.go b/ingest/ledger_transaction.go index d6bf544471..c53b6f35fa 100644 --- a/ingest/ledger_transaction.go +++ b/ingest/ledger_transaction.go @@ -54,27 +54,8 @@ func (t *LedgerTransaction) GetChanges() ([]Change, error) { // These changes reflect the ledgerEntry changes that were caused by the operations in the transaction // Populate the operationInfo for these changes in the `Change` struct - // TODO: Refactor this to use LedgerTransaction.GetOperationChanges - for opIdx, operationMeta := range v1Meta.Operations { - opChanges := GetChangesFromLedgerEntryChanges( - operationMeta.Changes, - ) - for _, change := range opChanges { - op, found := t.GetOperation(uint32(opIdx)) - if !found { - continue - } - results, _ := t.Result.OperationResults() - operationResult := results[opIdx].MustTr() - operationInfo := OperationInfo{ - operationIdx: uint32(opIdx), - operation: &op, - operationResult: &operationResult, - txEnvelope: &t.Envelope, - } - change.operationInfo = &operationInfo - change.isOperationChange = true - } + for opIdx, _ := range v1Meta.Operations { + opChanges := t.operationChanges(v1Meta.Operations, uint32(opIdx)) changes = append(changes, opChanges...) } case 2, 3: @@ -107,27 +88,8 @@ func (t *LedgerTransaction) GetChanges() ([]Change, error) { return changes, nil } - // TODO: Refactor this to use LedgerTransaction.GetOperationChanges - for opIdx, operationMetaChanges := range operationMeta { - opChanges := GetChangesFromLedgerEntryChanges( - operationMetaChanges.Changes, - ) - for _, change := range opChanges { - op, found := t.GetOperation(uint32(opIdx)) - if !found { - continue - } - results, _ := t.Result.OperationResults() - operationResult := results[opIdx].MustTr() - operationInfo := OperationInfo{ - operationIdx: uint32(opIdx), - operation: &op, - operationResult: &operationResult, - txEnvelope: &t.Envelope, - } - change.operationInfo = &operationInfo - change.isOperationChange = true - } + for opIdx, _ := range operationMeta { + opChanges := t.operationChanges(operationMeta, uint32(opIdx)) changes = append(changes, opChanges...) } @@ -152,15 +114,13 @@ func (t *LedgerTransaction) GetOperation(index uint32) (xdr.Operation, bool) { // GetOperationChanges returns a developer friendly representation of LedgerEntryChanges. // It contains only operation changes. func (t *LedgerTransaction) GetOperationChanges(operationIndex uint32) ([]Change, error) { - changes := []Change{} - if t.UnsafeMeta.V == 0 { - return changes, errors.New("TransactionMeta.V=0 not supported") + return []Change{}, errors.New("TransactionMeta.V=0 not supported") } // Ignore operations meta if txInternalError https://github.com/stellar/go/issues/2111 if t.txInternalError() && t.LedgerVersion <= 12 { - return changes, nil + return []Change{}, nil } var operationMeta []xdr.OperationMeta @@ -172,19 +132,15 @@ func (t *LedgerTransaction) GetOperationChanges(operationIndex uint32) ([]Change case 3: operationMeta = t.UnsafeMeta.MustV3().Operations default: - return changes, errors.New("Unsupported TransactionMeta version") + return []Change{}, errors.New("Unsupported TransactionMeta version") } - changes, err := t.operationChanges(operationMeta, operationIndex) - if err != nil { - return []Change{}, err - } - return changes, nil + return t.operationChanges(operationMeta, operationIndex), nil } -func (t *LedgerTransaction) operationChanges(ops []xdr.OperationMeta, index uint32) ([]Change, error) { +func (t *LedgerTransaction) operationChanges(ops []xdr.OperationMeta, index uint32) []Change { if int(index) >= len(ops) { - return []Change{}, nil // TODO - operations_processor somehow seems to be failing without this + return []Change{} // TODO - operations_processor somehow seems to be failing without this } operationMeta := ops[index] @@ -196,7 +152,10 @@ func (t *LedgerTransaction) operationChanges(ops []xdr.OperationMeta, index uint if !found { continue } - results, _ := t.Result.OperationResults() + results, ok := t.Result.OperationResults() + if !ok || len(results) == 0 { // This shouldnt happen. + continue + } operationResult := results[index].MustTr() operationInfo := OperationInfo{ operationIdx: index, @@ -207,7 +166,7 @@ func (t *LedgerTransaction) operationChanges(ops []xdr.OperationMeta, index uint change.operationInfo = &operationInfo change.isOperationChange = true } - return changes, nil + return changes } // GetDiagnosticEvents returns all contract events emitted by a given operation. From 070c5525fae7e06c54351630b12c7aff6660f886 Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Wed, 20 Nov 2024 23:06:40 -0800 Subject: [PATCH 05/29] reformat comments --- ingest/ledger_transaction.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ingest/ledger_transaction.go b/ingest/ledger_transaction.go index c53b6f35fa..29fc01a66a 100644 --- a/ingest/ledger_transaction.go +++ b/ingest/ledger_transaction.go @@ -54,7 +54,11 @@ func (t *LedgerTransaction) GetChanges() ([]Change, error) { // These changes reflect the ledgerEntry changes that were caused by the operations in the transaction // Populate the operationInfo for these changes in the `Change` struct - for opIdx, _ := range v1Meta.Operations { + operationMeta := v1Meta.Operations + // operationMeta is a list of lists. + // Each element in operationMeta is a list of ledgerEntryChanges + // caused by the operation at that index of the element + for opIdx, _ := range operationMeta { opChanges := t.operationChanges(v1Meta.Operations, uint32(opIdx)) changes = append(changes, opChanges...) } @@ -88,6 +92,9 @@ func (t *LedgerTransaction) GetChanges() ([]Change, error) { return changes, nil } + // operationMeta is a list of lists. + // Each element in operationMeta is a list of ledgerEntryChanges + // caused by the operation at that index of the element for opIdx, _ := range operationMeta { opChanges := t.operationChanges(operationMeta, uint32(opIdx)) changes = append(changes, opChanges...) From 26780fa82b9b91a8265e1e2b797ea6d1250d49b7 Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Thu, 21 Nov 2024 00:31:31 -0800 Subject: [PATCH 06/29] Add all types of change reasons to the Change struct. Simplify LedgerTransaction.GetChanges() --- ingest/change.go | 33 +++++++++++++----- ingest/ledger_change_reader.go | 10 ++++-- ingest/ledger_transaction.go | 61 +++++++++++++++++++++------------- 3 files changed, 68 insertions(+), 36 deletions(-) diff --git a/ingest/change.go b/ingest/change.go index 50dc95f300..57c2328ce4 100644 --- a/ingest/change.go +++ b/ingest/change.go @@ -18,18 +18,33 @@ import ( // If an entry is removed: Pre is not nil and Post is nil. // If this change is caused by a operation in a transaction, include the operation information. Wont work when changes are compacted type Change struct { - Type xdr.LedgerEntryType - Pre *xdr.LedgerEntry - Post *xdr.LedgerEntry - isOperationChange bool - operationInfo *OperationInfo + Type xdr.LedgerEntryType + Pre *xdr.LedgerEntry + Post *xdr.LedgerEntry + reason LedgerEntryChangeReason + TransactionData *TransactionEnvelopeAndResult + operationInfo *OperationInfo +} + +type LedgerEntryChangeReason uint16 + +const ( + Unknown LedgerEntryChangeReason = iota + Operation + Transaction + FeeChange + ProtocolUpgrade + Eviction +) + +type TransactionEnvelopeAndResult struct { + Envelope *xdr.TransactionEnvelope + Result *xdr.TransactionResultPair } type OperationInfo struct { - operationIdx uint32 - operation *xdr.Operation - operationResult *xdr.OperationResultTr - txEnvelope *xdr.TransactionEnvelope + operationIdx uint32 + operation *xdr.Operation } // String returns a best effort string representation of the change. diff --git a/ingest/ledger_change_reader.go b/ingest/ledger_change_reader.go index 496dc98b40..4cd030c66d 100644 --- a/ingest/ledger_change_reader.go +++ b/ingest/ledger_change_reader.go @@ -185,9 +185,10 @@ func (r *LedgerChangeReader) Read() (Change, error) { entry := entries[i] // when a ledger entry is evicted it is removed from the ledger changes[i] = Change{ - Type: entry.Data.Type, - Pre: &entry, - Post: nil, + Type: entry.Data.Type, + Pre: &entry, + Post: nil, + reason: Eviction, } } sortChanges(changes) @@ -200,6 +201,9 @@ func (r *LedgerChangeReader) Read() (Change, error) { changes := GetChangesFromLedgerEntryChanges( r.LedgerTransactionReader.lcm.UpgradesProcessing()[r.upgradeIndex].Changes, ) + for _, change := range changes { + change.reason = ProtocolUpgrade // Is there any other information that we can add here? + } r.pending = append(r.pending, changes...) r.upgradeIndex++ return r.Read() diff --git a/ingest/ledger_transaction.go b/ingest/ledger_transaction.go index 29fc01a66a..05b71b6652 100644 --- a/ingest/ledger_transaction.go +++ b/ingest/ledger_transaction.go @@ -26,13 +26,30 @@ func (t *LedgerTransaction) txInternalError() bool { // GetFeeChanges returns a developer friendly representation of LedgerEntryChanges // connected to fees. func (t *LedgerTransaction) GetFeeChanges() []Change { - return GetChangesFromLedgerEntryChanges(t.FeeChanges) + changes := GetChangesFromLedgerEntryChanges(t.FeeChanges) + txData := &TransactionEnvelopeAndResult{Envelope: &t.Envelope, Result: &t.Result} + for _, change := range changes { + change.reason = FeeChange + change.TransactionData = txData + } + return changes } // GetChanges returns a developer friendly representation of LedgerEntryChanges. // It contains transaction changes and operation changes in that order. If the // transaction failed with TxInternalError, operations and txChangesAfter are // omitted. It doesn't support legacy TransactionMeta.V=0. + +func (t *LedgerTransaction) getTransactionChanges(ledgerEntryChanges xdr.LedgerEntryChanges) []Change { + changes := GetChangesFromLedgerEntryChanges(ledgerEntryChanges) + txData := &TransactionEnvelopeAndResult{Envelope: &t.Envelope, Result: &t.Result} + for _, change := range changes { + change.reason = Transaction + change.TransactionData = txData + } + return changes +} + func (t *LedgerTransaction) GetChanges() ([]Change, error) { var changes []Change @@ -43,7 +60,7 @@ func (t *LedgerTransaction) GetChanges() ([]Change, error) { case 1: v1Meta := t.UnsafeMeta.MustV1() // The var `txChanges` reflect the ledgerEntryChanges that are changed because of the transaction as a whole - txChanges := GetChangesFromLedgerEntryChanges(v1Meta.TxChanges) + txChanges := t.getTransactionChanges(v1Meta.TxChanges) changes = append(changes, txChanges...) // Ignore operations meta if txInternalError https://github.com/stellar/go/issues/2111 @@ -58,7 +75,7 @@ func (t *LedgerTransaction) GetChanges() ([]Change, error) { // operationMeta is a list of lists. // Each element in operationMeta is a list of ledgerEntryChanges // caused by the operation at that index of the element - for opIdx, _ := range operationMeta { + for opIdx := range operationMeta { opChanges := t.operationChanges(v1Meta.Operations, uint32(opIdx)) changes = append(changes, opChanges...) } @@ -83,7 +100,7 @@ func (t *LedgerTransaction) GetChanges() ([]Change, error) { panic("Invalid meta version, expected 2 or 3") } - txChangesBefore := GetChangesFromLedgerEntryChanges(txBeforeChanges) + txChangesBefore := t.getTransactionChanges(txBeforeChanges) changes = append(changes, txChangesBefore...) // Ignore operations meta and txChangesAfter if txInternalError @@ -95,12 +112,12 @@ func (t *LedgerTransaction) GetChanges() ([]Change, error) { // operationMeta is a list of lists. // Each element in operationMeta is a list of ledgerEntryChanges // caused by the operation at that index of the element - for opIdx, _ := range operationMeta { + for opIdx := range operationMeta { opChanges := t.operationChanges(operationMeta, uint32(opIdx)) changes = append(changes, opChanges...) } - txChangesAfter := GetChangesFromLedgerEntryChanges(txAfterChanges) + txChangesAfter := t.getTransactionChanges(txAfterChanges) changes = append(changes, txChangesAfter...) default: return changes, errors.New("Unsupported TransactionMeta version") @@ -151,29 +168,25 @@ func (t *LedgerTransaction) operationChanges(ops []xdr.OperationMeta, index uint } operationMeta := ops[index] - changes := GetChangesFromLedgerEntryChanges( - operationMeta.Changes, - ) + changes := GetChangesFromLedgerEntryChanges(operationMeta.Changes) + op, found := t.GetOperation(index) + operationInfo := &OperationInfo{ + operationIdx: index, + operation: &op, + } + txData := &TransactionEnvelopeAndResult{Envelope: &t.Envelope, Result: &t.Result} + + res := make([]Change, 0, len(changes)) for _, change := range changes { - op, found := t.GetOperation(index) if !found { continue } - results, ok := t.Result.OperationResults() - if !ok || len(results) == 0 { // This shouldnt happen. - continue - } - operationResult := results[index].MustTr() - operationInfo := OperationInfo{ - operationIdx: index, - operation: &op, - operationResult: &operationResult, - txEnvelope: &t.Envelope, - } - change.operationInfo = &operationInfo - change.isOperationChange = true + change.operationInfo = operationInfo + change.reason = Operation + change.TransactionData = txData + res = append(res, change) } - return changes + return res } // GetDiagnosticEvents returns all contract events emitted by a given operation. From b1684152ddc88c4862969dd9375807be21ea396d Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Thu, 21 Nov 2024 08:29:42 -0800 Subject: [PATCH 07/29] Add LCM and transaction info altogether in change entry --- ingest/change.go | 13 ++++++------ ingest/ledger_change_reader.go | 6 ++++-- ingest/ledger_transaction.go | 33 +++++++++++------------------ ingest/ledger_transaction_reader.go | 1 + 4 files changed, 24 insertions(+), 29 deletions(-) diff --git a/ingest/change.go b/ingest/change.go index 57c2328ce4..727c69f1cc 100644 --- a/ingest/change.go +++ b/ingest/change.go @@ -18,12 +18,13 @@ import ( // If an entry is removed: Pre is not nil and Post is nil. // If this change is caused by a operation in a transaction, include the operation information. Wont work when changes are compacted type Change struct { - Type xdr.LedgerEntryType - Pre *xdr.LedgerEntry - Post *xdr.LedgerEntry - reason LedgerEntryChangeReason - TransactionData *TransactionEnvelopeAndResult - operationInfo *OperationInfo + Type xdr.LedgerEntryType + Pre *xdr.LedgerEntry + Post *xdr.LedgerEntry + Reason LedgerEntryChangeReason + operationIdx uint32 + tx *LedgerTransaction + lcm *xdr.LedgerCloseMeta } type LedgerEntryChangeReason uint16 diff --git a/ingest/ledger_change_reader.go b/ingest/ledger_change_reader.go index 4cd030c66d..3a53402e49 100644 --- a/ingest/ledger_change_reader.go +++ b/ingest/ledger_change_reader.go @@ -188,7 +188,8 @@ func (r *LedgerChangeReader) Read() (Change, error) { Type: entry.Data.Type, Pre: &entry, Post: nil, - reason: Eviction, + Reason: Eviction, + lcm: &r.lcm, } } sortChanges(changes) @@ -202,7 +203,8 @@ func (r *LedgerChangeReader) Read() (Change, error) { r.LedgerTransactionReader.lcm.UpgradesProcessing()[r.upgradeIndex].Changes, ) for _, change := range changes { - change.reason = ProtocolUpgrade // Is there any other information that we can add here? + change.Reason = ProtocolUpgrade // Is there any other information that we can add here? + change.lcm = &r.lcm } r.pending = append(r.pending, changes...) r.upgradeIndex++ diff --git a/ingest/ledger_transaction.go b/ingest/ledger_transaction.go index 05b71b6652..1a9d9f3000 100644 --- a/ingest/ledger_transaction.go +++ b/ingest/ledger_transaction.go @@ -17,6 +17,7 @@ type LedgerTransaction struct { FeeChanges xdr.LedgerEntryChanges UnsafeMeta xdr.TransactionMeta LedgerVersion uint32 + lcm *xdr.LedgerCloseMeta // This is read-only and not to be modified by downstream functions } func (t *LedgerTransaction) txInternalError() bool { @@ -27,10 +28,10 @@ func (t *LedgerTransaction) txInternalError() bool { // connected to fees. func (t *LedgerTransaction) GetFeeChanges() []Change { changes := GetChangesFromLedgerEntryChanges(t.FeeChanges) - txData := &TransactionEnvelopeAndResult{Envelope: &t.Envelope, Result: &t.Result} for _, change := range changes { - change.reason = FeeChange - change.TransactionData = txData + change.Reason = FeeChange + change.tx = t + change.lcm = t.lcm } return changes } @@ -42,10 +43,10 @@ func (t *LedgerTransaction) GetFeeChanges() []Change { func (t *LedgerTransaction) getTransactionChanges(ledgerEntryChanges xdr.LedgerEntryChanges) []Change { changes := GetChangesFromLedgerEntryChanges(ledgerEntryChanges) - txData := &TransactionEnvelopeAndResult{Envelope: &t.Envelope, Result: &t.Result} for _, change := range changes { - change.reason = Transaction - change.TransactionData = txData + change.Reason = Transaction + change.tx = t + change.lcm = t.lcm } return changes } @@ -169,24 +170,14 @@ func (t *LedgerTransaction) operationChanges(ops []xdr.OperationMeta, index uint operationMeta := ops[index] changes := GetChangesFromLedgerEntryChanges(operationMeta.Changes) - op, found := t.GetOperation(index) - operationInfo := &OperationInfo{ - operationIdx: index, - operation: &op, - } - txData := &TransactionEnvelopeAndResult{Envelope: &t.Envelope, Result: &t.Result} - res := make([]Change, 0, len(changes)) for _, change := range changes { - if !found { - continue - } - change.operationInfo = operationInfo - change.reason = Operation - change.TransactionData = txData - res = append(res, change) + change.Reason = Operation + change.tx = t + change.operationIdx = index + change.lcm = t.lcm } - return res + return changes } // GetDiagnosticEvents returns all contract events emitted by a given operation. diff --git a/ingest/ledger_transaction_reader.go b/ingest/ledger_transaction_reader.go index 5d2ad1d237..b975299576 100644 --- a/ingest/ledger_transaction_reader.go +++ b/ingest/ledger_transaction_reader.go @@ -94,6 +94,7 @@ func (reader *LedgerTransactionReader) Read() (LedgerTransaction, error) { UnsafeMeta: reader.lcm.TxApplyProcessing(i), FeeChanges: reader.lcm.FeeProcessing(i), LedgerVersion: uint32(reader.lcm.LedgerHeaderHistoryEntry().Header.LedgerVersion), + lcm: &reader.lcm, }, nil } From 29bc9bc133c1664e727df479e02ef8efc23890ff Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Thu, 21 Nov 2024 11:46:40 -0800 Subject: [PATCH 08/29] Wrte help doc for Change Entry --- ingest/change.go | 78 +++++++++++++------ ingest/ledger_change_reader.go | 10 ++- ingest/ledger_transaction.go | 13 ++-- ingest/ledger_transaction_test.go | 2 +- .../ingest/processors/operations_processor.go | 2 +- 5 files changed, 71 insertions(+), 34 deletions(-) diff --git a/ingest/change.go b/ingest/change.go index 727c69f1cc..99e9000496 100644 --- a/ingest/change.go +++ b/ingest/change.go @@ -13,18 +13,62 @@ import ( // It also provides some helper functions to quickly check if a given // change has occurred in an entry. // -// If an entry is created: Pre is nil and Post is not nil. -// If an entry is updated: Pre is not nil and Post is not nil. -// If an entry is removed: Pre is not nil and Post is nil. -// If this change is caused by a operation in a transaction, include the operation information. Wont work when changes are compacted +// Change represents a modification to a ledger entry, capturing both the before and after states +// of the entry along with the context that explains what caused the change. It is primarily used to +// track changes during transactions and/or operations within a transaction +// and can be helpful in identifying the specific cause of changes to the LedgerEntry state. (https://github.com/stellar/go/issues/5535 +// +// Behavior: +// +// - **Created entries**: Pre is nil, and Post is not nil. +// +// - **Updated entries**: Both Pre and Post are non-nil. +// +// - **Removed entries**: Pre is not nil, and Post is nil. +// +// A `Change` can be caused primarily by either a transaction or by an operation within a transaction: +// +// - **Operations**: +// Each successful operation can cause multiple ledger entry changes. +// For example, a path payment operation may affect the source and destination account entries, +// as well as potentially modify offers and/or liquidity pools. +// +// - **Transactions**: +// Some ledger changes, such as those involving fees or account balances, may be caused by +// the transaction itself and may not be tied to a specific operation within a transaction. +// For instance, fees for all operations in a transaction are debited from the source account, +// triggering ledger changes without operation-specific details. +// +// Fields: +// +// - Type: The type of the ledger entry being changed +// +// - Pre: The state of the ledger entry before the change. This will be nil if the entry was created. +// +// - Post: The state of the ledger entry after the change. This will be nil if the entry was removed. +// +// - Reason: The reason for the ledger entry change, represented by LedgerEntryChangeReason. +// +// - OperationIdx: The index of the operation in the transaction that caused the change. +// This field is relevant when the change is due to an operation within a transaction +// and won't be used when the change is compacted. +// +// - Tx: A reference to the LedgerTransaction that caused the change. +// +// - Lcm: The LedgerCloseMeta that precipitated the change. +// This is useful only when the Change is caused by an upgrade or by an eviction, i.e. outside a Transaction +// For changes caused by transaction or operations, look at the LedgerTransaction entity within Change +// +// - LedgerUpgrade: Information about the upgrade, if the change occurred as part of an upgrade. type Change struct { - Type xdr.LedgerEntryType - Pre *xdr.LedgerEntry - Post *xdr.LedgerEntry - Reason LedgerEntryChangeReason - operationIdx uint32 - tx *LedgerTransaction - lcm *xdr.LedgerCloseMeta + Type xdr.LedgerEntryType + Pre *xdr.LedgerEntry + Post *xdr.LedgerEntry + Reason LedgerEntryChangeReason + OperationIdx uint32 + Tx *LedgerTransaction + Lcm *xdr.LedgerCloseMeta + LedgerUpgrade *xdr.LedgerUpgrade } type LedgerEntryChangeReason uint16 @@ -34,20 +78,10 @@ const ( Operation Transaction FeeChange - ProtocolUpgrade + Upgrade Eviction ) -type TransactionEnvelopeAndResult struct { - Envelope *xdr.TransactionEnvelope - Result *xdr.TransactionResultPair -} - -type OperationInfo struct { - operationIdx uint32 - operation *xdr.Operation -} - // String returns a best effort string representation of the change. // If the Pre or Post xdr is invalid, the field will be omitted from the string. func (c Change) String() string { diff --git a/ingest/ledger_change_reader.go b/ingest/ledger_change_reader.go index 3a53402e49..91d13fcc77 100644 --- a/ingest/ledger_change_reader.go +++ b/ingest/ledger_change_reader.go @@ -175,6 +175,7 @@ func (r *LedgerChangeReader) Read() (Change, error) { r.pending = append(r.pending, metaChanges...) } return r.Read() + case evictionChangesState: entries, err := r.lcm.EvictedPersistentLedgerEntries() if err != nil { @@ -189,22 +190,25 @@ func (r *LedgerChangeReader) Read() (Change, error) { Pre: &entry, Post: nil, Reason: Eviction, - lcm: &r.lcm, + Lcm: &r.lcm, } } sortChanges(changes) r.pending = append(r.pending, changes...) r.state++ return r.Read() + case upgradeChangesState: // Get upgrade changes if r.upgradeIndex < len(r.LedgerTransactionReader.lcm.UpgradesProcessing()) { changes := GetChangesFromLedgerEntryChanges( r.LedgerTransactionReader.lcm.UpgradesProcessing()[r.upgradeIndex].Changes, ) + ledgerUpgrades := r.LedgerTransactionReader.lcm.UpgradesProcessing() for _, change := range changes { - change.Reason = ProtocolUpgrade // Is there any other information that we can add here? - change.lcm = &r.lcm + change.Reason = Upgrade + change.Lcm = &r.lcm + change.LedgerUpgrade = &ledgerUpgrades[r.upgradeIndex].Upgrade } r.pending = append(r.pending, changes...) r.upgradeIndex++ diff --git a/ingest/ledger_transaction.go b/ingest/ledger_transaction.go index 1a9d9f3000..6996903e2b 100644 --- a/ingest/ledger_transaction.go +++ b/ingest/ledger_transaction.go @@ -30,8 +30,7 @@ func (t *LedgerTransaction) GetFeeChanges() []Change { changes := GetChangesFromLedgerEntryChanges(t.FeeChanges) for _, change := range changes { change.Reason = FeeChange - change.tx = t - change.lcm = t.lcm + change.Tx = t } return changes } @@ -45,8 +44,8 @@ func (t *LedgerTransaction) getTransactionChanges(ledgerEntryChanges xdr.LedgerE changes := GetChangesFromLedgerEntryChanges(ledgerEntryChanges) for _, change := range changes { change.Reason = Transaction - change.tx = t - change.lcm = t.lcm + change.Tx = t + change.Lcm = t.lcm } return changes } @@ -173,9 +172,9 @@ func (t *LedgerTransaction) operationChanges(ops []xdr.OperationMeta, index uint for _, change := range changes { change.Reason = Operation - change.tx = t - change.operationIdx = index - change.lcm = t.lcm + change.Tx = t + change.OperationIdx = index + change.Lcm = t.lcm } return changes } diff --git a/ingest/ledger_transaction_test.go b/ingest/ledger_transaction_test.go index ced92e6918..545c5c6aee 100644 --- a/ingest/ledger_transaction_test.go +++ b/ingest/ledger_transaction_test.go @@ -336,7 +336,7 @@ func TestFeeMetaAndOperationsChangesSeparate(t *testing.T) { assert.Equal(t, operationChanges[0].Pre.Data.MustAccount().Balance, xdr.Int64(300)) assert.Equal(t, operationChanges[0].Post.Data.MustAccount().Balance, xdr.Int64(400)) - // Ignore operation meta if tx result is txInternalError + // Ignore operation meta if Tx result is txInternalError // https://github.com/stellar/go/issues/2111 tx.Result.Result.Result.Code = xdr.TransactionResultCodeTxInternalError metaChanges, err = tx.GetChanges() diff --git a/services/horizon/internal/ingest/processors/operations_processor.go b/services/horizon/internal/ingest/processors/operations_processor.go index 39d09b1608..80e834df4e 100644 --- a/services/horizon/internal/ingest/processors/operations_processor.go +++ b/services/horizon/internal/ingest/processors/operations_processor.go @@ -324,7 +324,7 @@ func addAccountAndMuxedAccountDetails(result map[string]interface{}, a xdr.Muxed // _muxed_id fields should had ideally been stored in the DB as a string instead of uint64 // due to Javascript not being able to handle them, see https://github.com/stellar/go/issues/3714 // However, we released this code in the wild before correcting it. Thus, what we do is - // work around it (by preprocessing it into a string) in Operation.UnmarshalDetails() + // work around it (by preprocessing it into a string) in OperationChange.UnmarshalDetails() result[prefix+"_muxed_id"] = uint64(a.Med25519.Id) } } From 73e4494916c7c34b1b85e55a261df640cc56e42e Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Thu, 21 Nov 2024 11:54:47 -0800 Subject: [PATCH 09/29] Add TxHash to LedgerTransaction struct --- ingest/ledger_transaction.go | 7 ++++--- ingest/ledger_transaction_reader.go | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ingest/ledger_transaction.go b/ingest/ledger_transaction.go index 6996903e2b..7eb0854396 100644 --- a/ingest/ledger_transaction.go +++ b/ingest/ledger_transaction.go @@ -17,7 +17,8 @@ type LedgerTransaction struct { FeeChanges xdr.LedgerEntryChanges UnsafeMeta xdr.TransactionMeta LedgerVersion uint32 - lcm *xdr.LedgerCloseMeta // This is read-only and not to be modified by downstream functions + Lcm *xdr.LedgerCloseMeta // This is read-only and not to be modified by downstream functions + Hash *xdr.Hash } func (t *LedgerTransaction) txInternalError() bool { @@ -45,7 +46,7 @@ func (t *LedgerTransaction) getTransactionChanges(ledgerEntryChanges xdr.LedgerE for _, change := range changes { change.Reason = Transaction change.Tx = t - change.Lcm = t.lcm + change.Lcm = t.Lcm } return changes } @@ -174,7 +175,7 @@ func (t *LedgerTransaction) operationChanges(ops []xdr.OperationMeta, index uint change.Reason = Operation change.Tx = t change.OperationIdx = index - change.Lcm = t.lcm + change.Lcm = t.Lcm } return changes } diff --git a/ingest/ledger_transaction_reader.go b/ingest/ledger_transaction_reader.go index b975299576..42f1363585 100644 --- a/ingest/ledger_transaction_reader.go +++ b/ingest/ledger_transaction_reader.go @@ -94,7 +94,8 @@ func (reader *LedgerTransactionReader) Read() (LedgerTransaction, error) { UnsafeMeta: reader.lcm.TxApplyProcessing(i), FeeChanges: reader.lcm.FeeProcessing(i), LedgerVersion: uint32(reader.lcm.LedgerHeaderHistoryEntry().Header.LedgerVersion), - lcm: &reader.lcm, + Lcm: &reader.lcm, + Hash: &hash, }, nil } From c06e241325e85de5288f4a6c5e70d1e3aee5bc3c Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Thu, 21 Nov 2024 12:06:17 -0800 Subject: [PATCH 10/29] reorg comments --- ingest/ledger_transaction.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ingest/ledger_transaction.go b/ingest/ledger_transaction.go index 7eb0854396..ca255e3711 100644 --- a/ingest/ledger_transaction.go +++ b/ingest/ledger_transaction.go @@ -36,11 +36,6 @@ func (t *LedgerTransaction) GetFeeChanges() []Change { return changes } -// GetChanges returns a developer friendly representation of LedgerEntryChanges. -// It contains transaction changes and operation changes in that order. If the -// transaction failed with TxInternalError, operations and txChangesAfter are -// omitted. It doesn't support legacy TransactionMeta.V=0. - func (t *LedgerTransaction) getTransactionChanges(ledgerEntryChanges xdr.LedgerEntryChanges) []Change { changes := GetChangesFromLedgerEntryChanges(ledgerEntryChanges) for _, change := range changes { @@ -51,6 +46,10 @@ func (t *LedgerTransaction) getTransactionChanges(ledgerEntryChanges xdr.LedgerE return changes } +// GetChanges returns a developer friendly representation of LedgerEntryChanges. +// It contains transaction changes and operation changes in that order. If the +// transaction failed with TxInternalError, operations and txChangesAfter are +// omitted. It doesn't support legacy TransactionMeta.V=0. func (t *LedgerTransaction) GetChanges() ([]Change, error) { var changes []Change From fe7cef8e67a5d4995e92feb5a99395cc7f582d81 Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Thu, 21 Nov 2024 12:14:16 -0800 Subject: [PATCH 11/29] Comments cleanup --- ingest/change.go | 3 ++- ingest/ledger_transaction.go | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ingest/change.go b/ingest/change.go index 99e9000496..432cadaa9a 100644 --- a/ingest/change.go +++ b/ingest/change.go @@ -54,10 +54,11 @@ import ( // and won't be used when the change is compacted. // // - Tx: A reference to the LedgerTransaction that caused the change. +// Contains information about Transaction, Hash, TxResultPair etc // // - Lcm: The LedgerCloseMeta that precipitated the change. // This is useful only when the Change is caused by an upgrade or by an eviction, i.e. outside a Transaction -// For changes caused by transaction or operations, look at the LedgerTransaction entity within Change +// For changes caused by transaction or operations, look at the Tx field // // - LedgerUpgrade: Information about the upgrade, if the change occurred as part of an upgrade. type Change struct { diff --git a/ingest/ledger_transaction.go b/ingest/ledger_transaction.go index ca255e3711..79f7c7b275 100644 --- a/ingest/ledger_transaction.go +++ b/ingest/ledger_transaction.go @@ -41,7 +41,6 @@ func (t *LedgerTransaction) getTransactionChanges(ledgerEntryChanges xdr.LedgerE for _, change := range changes { change.Reason = Transaction change.Tx = t - change.Lcm = t.Lcm } return changes } @@ -174,7 +173,6 @@ func (t *LedgerTransaction) operationChanges(ops []xdr.OperationMeta, index uint change.Reason = Operation change.Tx = t change.OperationIdx = index - change.Lcm = t.Lcm } return changes } From 06c515c402ff6499b4da74eeb93825b04e72a5a0 Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Sun, 24 Nov 2024 21:12:23 -0800 Subject: [PATCH 12/29] Address PR review changes --- ingest/change.go | 16 ++++++++-------- ingest/ledger_transaction.go | 12 ++++++------ ingest/ledger_transaction_reader.go | 4 ++-- ingest/ledger_transaction_test.go | 2 +- .../ingest/processors/operations_processor.go | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/ingest/change.go b/ingest/change.go index 432cadaa9a..7a3c0961e3 100644 --- a/ingest/change.go +++ b/ingest/change.go @@ -62,14 +62,14 @@ import ( // // - LedgerUpgrade: Information about the upgrade, if the change occurred as part of an upgrade. type Change struct { - Type xdr.LedgerEntryType - Pre *xdr.LedgerEntry - Post *xdr.LedgerEntry - Reason LedgerEntryChangeReason - OperationIdx uint32 - Tx *LedgerTransaction - Lcm *xdr.LedgerCloseMeta - LedgerUpgrade *xdr.LedgerUpgrade + Type xdr.LedgerEntryType + Pre *xdr.LedgerEntry + Post *xdr.LedgerEntry + Reason LedgerEntryChangeReason + OperationIndex uint32 + Transaction *LedgerTransaction + LEdger *xdr.LedgerCloseMeta + LedgerUpgrade *xdr.LedgerUpgrade } type LedgerEntryChangeReason uint16 diff --git a/ingest/ledger_transaction.go b/ingest/ledger_transaction.go index 79f7c7b275..3c54cd5764 100644 --- a/ingest/ledger_transaction.go +++ b/ingest/ledger_transaction.go @@ -17,8 +17,8 @@ type LedgerTransaction struct { FeeChanges xdr.LedgerEntryChanges UnsafeMeta xdr.TransactionMeta LedgerVersion uint32 - Lcm *xdr.LedgerCloseMeta // This is read-only and not to be modified by downstream functions - Hash *xdr.Hash + Ledger xdr.LedgerCloseMeta // This is read-only and not to be modified by downstream functions + Hash xdr.Hash } func (t *LedgerTransaction) txInternalError() bool { @@ -31,7 +31,7 @@ func (t *LedgerTransaction) GetFeeChanges() []Change { changes := GetChangesFromLedgerEntryChanges(t.FeeChanges) for _, change := range changes { change.Reason = FeeChange - change.Tx = t + change.Transaction = t } return changes } @@ -40,7 +40,7 @@ func (t *LedgerTransaction) getTransactionChanges(ledgerEntryChanges xdr.LedgerE changes := GetChangesFromLedgerEntryChanges(ledgerEntryChanges) for _, change := range changes { change.Reason = Transaction - change.Tx = t + change.Transaction = t } return changes } @@ -171,8 +171,8 @@ func (t *LedgerTransaction) operationChanges(ops []xdr.OperationMeta, index uint for _, change := range changes { change.Reason = Operation - change.Tx = t - change.OperationIdx = index + change.Transaction = t + change.OperationIndex = index } return changes } diff --git a/ingest/ledger_transaction_reader.go b/ingest/ledger_transaction_reader.go index 42f1363585..bbe11a15b8 100644 --- a/ingest/ledger_transaction_reader.go +++ b/ingest/ledger_transaction_reader.go @@ -94,8 +94,8 @@ func (reader *LedgerTransactionReader) Read() (LedgerTransaction, error) { UnsafeMeta: reader.lcm.TxApplyProcessing(i), FeeChanges: reader.lcm.FeeProcessing(i), LedgerVersion: uint32(reader.lcm.LedgerHeaderHistoryEntry().Header.LedgerVersion), - Lcm: &reader.lcm, - Hash: &hash, + Ledger: reader.lcm, + Hash: hash, }, nil } diff --git a/ingest/ledger_transaction_test.go b/ingest/ledger_transaction_test.go index 545c5c6aee..ced92e6918 100644 --- a/ingest/ledger_transaction_test.go +++ b/ingest/ledger_transaction_test.go @@ -336,7 +336,7 @@ func TestFeeMetaAndOperationsChangesSeparate(t *testing.T) { assert.Equal(t, operationChanges[0].Pre.Data.MustAccount().Balance, xdr.Int64(300)) assert.Equal(t, operationChanges[0].Post.Data.MustAccount().Balance, xdr.Int64(400)) - // Ignore operation meta if Tx result is txInternalError + // Ignore operation meta if tx result is txInternalError // https://github.com/stellar/go/issues/2111 tx.Result.Result.Result.Code = xdr.TransactionResultCodeTxInternalError metaChanges, err = tx.GetChanges() diff --git a/services/horizon/internal/ingest/processors/operations_processor.go b/services/horizon/internal/ingest/processors/operations_processor.go index 80e834df4e..39d09b1608 100644 --- a/services/horizon/internal/ingest/processors/operations_processor.go +++ b/services/horizon/internal/ingest/processors/operations_processor.go @@ -324,7 +324,7 @@ func addAccountAndMuxedAccountDetails(result map[string]interface{}, a xdr.Muxed // _muxed_id fields should had ideally been stored in the DB as a string instead of uint64 // due to Javascript not being able to handle them, see https://github.com/stellar/go/issues/3714 // However, we released this code in the wild before correcting it. Thus, what we do is - // work around it (by preprocessing it into a string) in OperationChange.UnmarshalDetails() + // work around it (by preprocessing it into a string) in Operation.UnmarshalDetails() result[prefix+"_muxed_id"] = uint64(a.Med25519.Id) } } From 01b86b012139d9699cb50fd79bb73f9ddfca5223 Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Sun, 24 Nov 2024 21:19:33 -0800 Subject: [PATCH 13/29] rename fields --- ingest/change.go | 2 +- ingest/ledger_change_reader.go | 4 +- .../integration/muxed_operations_test.go | 77 +++++++++++++++++++ .../internal/integration/parameters_test.go | 8 +- .../internal/test/integration/integration.go | 19 +++-- xdr/transaction_envelope.go | 4 - 6 files changed, 95 insertions(+), 19 deletions(-) diff --git a/ingest/change.go b/ingest/change.go index 7a3c0961e3..0e2d72967b 100644 --- a/ingest/change.go +++ b/ingest/change.go @@ -68,7 +68,7 @@ type Change struct { Reason LedgerEntryChangeReason OperationIndex uint32 Transaction *LedgerTransaction - LEdger *xdr.LedgerCloseMeta + Ledger *xdr.LedgerCloseMeta LedgerUpgrade *xdr.LedgerUpgrade } diff --git a/ingest/ledger_change_reader.go b/ingest/ledger_change_reader.go index 91d13fcc77..0c7bc8e4ec 100644 --- a/ingest/ledger_change_reader.go +++ b/ingest/ledger_change_reader.go @@ -190,7 +190,7 @@ func (r *LedgerChangeReader) Read() (Change, error) { Pre: &entry, Post: nil, Reason: Eviction, - Lcm: &r.lcm, + Ledger: &r.lcm, } } sortChanges(changes) @@ -207,7 +207,7 @@ func (r *LedgerChangeReader) Read() (Change, error) { ledgerUpgrades := r.LedgerTransactionReader.lcm.UpgradesProcessing() for _, change := range changes { change.Reason = Upgrade - change.Lcm = &r.lcm + change.Ledger = &r.lcm change.LedgerUpgrade = &ledgerUpgrades[r.upgradeIndex].Upgrade } r.pending = append(r.pending, changes...) diff --git a/services/horizon/internal/integration/muxed_operations_test.go b/services/horizon/internal/integration/muxed_operations_test.go index 53d9fbf23c..f8144f34a3 100644 --- a/services/horizon/internal/integration/muxed_operations_test.go +++ b/services/horizon/internal/integration/muxed_operations_test.go @@ -1,7 +1,14 @@ package integration import ( + "context" + "fmt" + "github.com/stellar/go/ingest" + "github.com/stellar/go/ingest/ledgerbackend" + "io" + "os" "testing" + "time" "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/keypair" @@ -131,4 +138,74 @@ func TestMuxedOperations(t *testing.T) { assert.True(t, oneSet, "at least one of account_muxed_id, seller_muxed_id must be set") } } + + time.Sleep(time.Second * 5) + + captiveCoreConfig := ledgerbackend.CaptiveCoreConfig{} + captiveCoreConfig.BinaryPath = os.Getenv("HORIZON_INTEGRATION_TESTS_CAPTIVE_CORE_BIN") + captiveCoreConfig.HistoryArchiveURLs = []string{itest.GetDefaultArgs()["history-archive-urls"]} + captiveCoreConfig.NetworkPassphrase = integration.StandaloneNetworkPassphrase + captiveCoreConfig.CheckpointFrequency = 8 + confName, _, cleanup := CreateCaptiveCoreConfig(SimpleCaptiveCoreToml) + kk := ledgerbackend.CaptiveCoreTomlParams{ + NetworkPassphrase: captiveCoreConfig.NetworkPassphrase, + HistoryArchiveURLs: captiveCoreConfig.HistoryArchiveURLs, + } + + captiveCoreToml, _ := ledgerbackend.NewCaptiveCoreTomlFromFile(confName, kk) + captiveCoreConfig.Toml = captiveCoreToml + defer cleanup() + + var captiveCore *ledgerbackend.CaptiveStellarCore + captiveCore, err = ledgerbackend.NewCaptive(captiveCoreConfig) + if err != nil { + t.Fatal(err) + } + cc := context.Background() + err = captiveCore.PrepareRange(cc, ledgerbackend.BoundedRange(uint32(txResp.Ledger), uint32(txResp.Ledger))) + defer captiveCore.Close() + + if err != nil { + t.Fatal(err) + } + + ll, _ := captiveCore.GetLedger(cc, uint32(txResp.Ledger)) + + var successfulTransactions, failedTransactions int + var operationsInSuccessful, operationsInFailed int + + txReader, _ := ingest.NewLedgerTransactionReaderFromLedgerCloseMeta( + captiveCoreConfig.NetworkPassphrase, ll, + ) + //panicIf(err) + defer txReader.Close() + + // Read each transaction within the ledger, extract its operations, and + // accumulate the statistics we're interested in. + for { + ltx, err := txReader.Read() + if err == io.EOF { + break + } + //panicIf(err) + + envelope := ltx.Envelope + operationCount := len(envelope.Operations()) + if ltx.Result.Successful() { + successfulTransactions++ + operationsInSuccessful += operationCount + } else { + failedTransactions++ + operationsInFailed += operationCount + } + } + + fmt.Println("\nDone. Results:") + fmt.Printf(" - total transactions: %d\n", successfulTransactions+failedTransactions) + fmt.Printf(" - succeeded / failed: %d / %d\n", successfulTransactions, failedTransactions) + fmt.Printf(" - total operations: %d\n", operationsInSuccessful+operationsInFailed) + fmt.Printf(" - succeeded / failed: %d / %d\n", operationsInSuccessful, operationsInFailed) + + t.Logf("----------- This is %v, %v", ll.LedgerSequence(), ll.TransactionHash(0)) + } diff --git a/services/horizon/internal/integration/parameters_test.go b/services/horizon/internal/integration/parameters_test.go index c7e0d0c75b..51bf8a5498 100644 --- a/services/horizon/internal/integration/parameters_test.go +++ b/services/horizon/internal/integration/parameters_test.go @@ -68,7 +68,7 @@ func TestBucketDirDisallowed(t *testing.T) { config := `BUCKET_DIR_PATH="/tmp" ` + SimpleCaptiveCoreToml - confName, _, cleanup := createCaptiveCoreConfig(config) + confName, _, cleanup := CreateCaptiveCoreConfig(config) defer cleanup() testConfig := integration.GetTestConfig() testConfig.HorizonIngestParameters = map[string]string{ @@ -252,7 +252,7 @@ func TestNetworkEnvironmentVariable(t *testing.T) { // Ensures that the filesystem ends up in the correct state with Captive Core. func TestCaptiveCoreConfigFilesystemState(t *testing.T) { - confName, storagePath, cleanup := createCaptiveCoreConfig(SimpleCaptiveCoreToml) + confName, storagePath, cleanup := CreateCaptiveCoreConfig(SimpleCaptiveCoreToml) defer cleanup() localParams := integration.MergeMaps(defaultCaptiveCoreParameters, map[string]string{ @@ -672,10 +672,10 @@ func validateCaptiveCoreDiskState(itest *integration.Test, rootDir string) { tt.FileExists(coreConf) } -// createCaptiveCoreConfig will create a temporary TOML config with the +// CreateCaptiveCoreConfig will create a temporary TOML config with the // specified contents as well as a temporary storage directory. You should // `defer` the returned function to clean these up when you're done. -func createCaptiveCoreConfig(contents string) (string, string, func()) { +func CreateCaptiveCoreConfig(contents string) (string, string, func()) { tomlFile, err := ioutil.TempFile("", "captive-core-test-*.toml") defer tomlFile.Close() if err != nil { diff --git a/services/horizon/internal/test/integration/integration.go b/services/horizon/internal/test/integration/integration.go index 107cb33759..c5b73bf470 100644 --- a/services/horizon/internal/test/integration/integration.go +++ b/services/horizon/internal/test/integration/integration.go @@ -362,8 +362,8 @@ func (i *Test) StartHorizon(startIngestProcess bool) error { // To facilitate custom runs of Horizon, we merge a default set of // parameters with the tester-supplied ones (if any). - mergedWebArgs := MergeMaps(i.getDefaultWebArgs(), i.config.HorizonWebParameters) - mergedIngestArgs := MergeMaps(i.getDefaultIngestArgs(), i.config.HorizonIngestParameters) + mergedWebArgs := MergeMaps(i.GetDefaultWebArgs(), i.config.HorizonWebParameters) + mergedIngestArgs := MergeMaps(i.GetDefaultIngestArgs(), i.config.HorizonIngestParameters) // Set up Horizon clients i.setupHorizonClient(mergedWebArgs) @@ -431,7 +431,7 @@ func (i *Test) StartHorizon(startIngestProcess bool) error { return nil } -func (i *Test) getDefaultArgs() map[string]string { +func (i *Test) GetDefaultArgs() map[string]string { // TODO: Ideally, we'd be pulling host/port information from the Docker // Compose YAML file itself rather than hardcoding it. return map[string]string{ @@ -449,12 +449,12 @@ func (i *Test) getDefaultArgs() map[string]string { } } -func (i *Test) getDefaultWebArgs() map[string]string { - return MergeMaps(i.getDefaultArgs(), map[string]string{"admin-port": "0"}) +func (i *Test) GetDefaultWebArgs() map[string]string { + return MergeMaps(i.GetDefaultArgs(), map[string]string{"admin-port": "0"}) } -func (i *Test) getDefaultIngestArgs() map[string]string { - return MergeMaps(i.getDefaultArgs(), map[string]string{ +func (i *Test) GetDefaultIngestArgs() map[string]string { + return MergeMaps(i.GetDefaultArgs(), map[string]string{ "admin-port": strconv.Itoa(i.AdminPort()), "port": "8001", "db-url": i.testDB.DSN, @@ -871,7 +871,10 @@ func (i *Test) WaitForHorizonIngest() { if root.HorizonSequence < 3 || int(root.HorizonSequence) != int(root.IngestSequence) { - i.t.Logf("Horizon ingesting... %v", root) + //jcart, _ := json.MarshalIndent(root, "", "\t") + + i.t.Logf("Horizon ingesting...") + //i.t.Logf(string(jcart)) continue } diff --git a/xdr/transaction_envelope.go b/xdr/transaction_envelope.go index b2259fedea..6d513ff342 100644 --- a/xdr/transaction_envelope.go +++ b/xdr/transaction_envelope.go @@ -215,10 +215,6 @@ func (e TransactionEnvelope) Preconditions() Preconditions { // Note for fee bump transactions, Operations() returns the operations // of the inner transaction func (e TransactionEnvelope) Operations() []Operation { - // This is not expected to happen. - if (e == TransactionEnvelope{}) { - return []Operation{} - } switch e.Type { case EnvelopeTypeEnvelopeTypeTxFeeBump: return e.FeeBump.Tx.InnerTx.V1.Tx.Operations From e363afc2e71318e00be2beedd9bf1a95da0719dc Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Sun, 24 Nov 2024 21:23:36 -0800 Subject: [PATCH 14/29] uncommit half baked changes --- .../integration/muxed_operations_test.go | 77 ------------------- .../internal/integration/parameters_test.go | 8 +- .../internal/test/integration/integration.go | 19 ++--- 3 files changed, 12 insertions(+), 92 deletions(-) diff --git a/services/horizon/internal/integration/muxed_operations_test.go b/services/horizon/internal/integration/muxed_operations_test.go index f8144f34a3..53d9fbf23c 100644 --- a/services/horizon/internal/integration/muxed_operations_test.go +++ b/services/horizon/internal/integration/muxed_operations_test.go @@ -1,14 +1,7 @@ package integration import ( - "context" - "fmt" - "github.com/stellar/go/ingest" - "github.com/stellar/go/ingest/ledgerbackend" - "io" - "os" "testing" - "time" "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/keypair" @@ -138,74 +131,4 @@ func TestMuxedOperations(t *testing.T) { assert.True(t, oneSet, "at least one of account_muxed_id, seller_muxed_id must be set") } } - - time.Sleep(time.Second * 5) - - captiveCoreConfig := ledgerbackend.CaptiveCoreConfig{} - captiveCoreConfig.BinaryPath = os.Getenv("HORIZON_INTEGRATION_TESTS_CAPTIVE_CORE_BIN") - captiveCoreConfig.HistoryArchiveURLs = []string{itest.GetDefaultArgs()["history-archive-urls"]} - captiveCoreConfig.NetworkPassphrase = integration.StandaloneNetworkPassphrase - captiveCoreConfig.CheckpointFrequency = 8 - confName, _, cleanup := CreateCaptiveCoreConfig(SimpleCaptiveCoreToml) - kk := ledgerbackend.CaptiveCoreTomlParams{ - NetworkPassphrase: captiveCoreConfig.NetworkPassphrase, - HistoryArchiveURLs: captiveCoreConfig.HistoryArchiveURLs, - } - - captiveCoreToml, _ := ledgerbackend.NewCaptiveCoreTomlFromFile(confName, kk) - captiveCoreConfig.Toml = captiveCoreToml - defer cleanup() - - var captiveCore *ledgerbackend.CaptiveStellarCore - captiveCore, err = ledgerbackend.NewCaptive(captiveCoreConfig) - if err != nil { - t.Fatal(err) - } - cc := context.Background() - err = captiveCore.PrepareRange(cc, ledgerbackend.BoundedRange(uint32(txResp.Ledger), uint32(txResp.Ledger))) - defer captiveCore.Close() - - if err != nil { - t.Fatal(err) - } - - ll, _ := captiveCore.GetLedger(cc, uint32(txResp.Ledger)) - - var successfulTransactions, failedTransactions int - var operationsInSuccessful, operationsInFailed int - - txReader, _ := ingest.NewLedgerTransactionReaderFromLedgerCloseMeta( - captiveCoreConfig.NetworkPassphrase, ll, - ) - //panicIf(err) - defer txReader.Close() - - // Read each transaction within the ledger, extract its operations, and - // accumulate the statistics we're interested in. - for { - ltx, err := txReader.Read() - if err == io.EOF { - break - } - //panicIf(err) - - envelope := ltx.Envelope - operationCount := len(envelope.Operations()) - if ltx.Result.Successful() { - successfulTransactions++ - operationsInSuccessful += operationCount - } else { - failedTransactions++ - operationsInFailed += operationCount - } - } - - fmt.Println("\nDone. Results:") - fmt.Printf(" - total transactions: %d\n", successfulTransactions+failedTransactions) - fmt.Printf(" - succeeded / failed: %d / %d\n", successfulTransactions, failedTransactions) - fmt.Printf(" - total operations: %d\n", operationsInSuccessful+operationsInFailed) - fmt.Printf(" - succeeded / failed: %d / %d\n", operationsInSuccessful, operationsInFailed) - - t.Logf("----------- This is %v, %v", ll.LedgerSequence(), ll.TransactionHash(0)) - } diff --git a/services/horizon/internal/integration/parameters_test.go b/services/horizon/internal/integration/parameters_test.go index 51bf8a5498..c7e0d0c75b 100644 --- a/services/horizon/internal/integration/parameters_test.go +++ b/services/horizon/internal/integration/parameters_test.go @@ -68,7 +68,7 @@ func TestBucketDirDisallowed(t *testing.T) { config := `BUCKET_DIR_PATH="/tmp" ` + SimpleCaptiveCoreToml - confName, _, cleanup := CreateCaptiveCoreConfig(config) + confName, _, cleanup := createCaptiveCoreConfig(config) defer cleanup() testConfig := integration.GetTestConfig() testConfig.HorizonIngestParameters = map[string]string{ @@ -252,7 +252,7 @@ func TestNetworkEnvironmentVariable(t *testing.T) { // Ensures that the filesystem ends up in the correct state with Captive Core. func TestCaptiveCoreConfigFilesystemState(t *testing.T) { - confName, storagePath, cleanup := CreateCaptiveCoreConfig(SimpleCaptiveCoreToml) + confName, storagePath, cleanup := createCaptiveCoreConfig(SimpleCaptiveCoreToml) defer cleanup() localParams := integration.MergeMaps(defaultCaptiveCoreParameters, map[string]string{ @@ -672,10 +672,10 @@ func validateCaptiveCoreDiskState(itest *integration.Test, rootDir string) { tt.FileExists(coreConf) } -// CreateCaptiveCoreConfig will create a temporary TOML config with the +// createCaptiveCoreConfig will create a temporary TOML config with the // specified contents as well as a temporary storage directory. You should // `defer` the returned function to clean these up when you're done. -func CreateCaptiveCoreConfig(contents string) (string, string, func()) { +func createCaptiveCoreConfig(contents string) (string, string, func()) { tomlFile, err := ioutil.TempFile("", "captive-core-test-*.toml") defer tomlFile.Close() if err != nil { diff --git a/services/horizon/internal/test/integration/integration.go b/services/horizon/internal/test/integration/integration.go index c5b73bf470..107cb33759 100644 --- a/services/horizon/internal/test/integration/integration.go +++ b/services/horizon/internal/test/integration/integration.go @@ -362,8 +362,8 @@ func (i *Test) StartHorizon(startIngestProcess bool) error { // To facilitate custom runs of Horizon, we merge a default set of // parameters with the tester-supplied ones (if any). - mergedWebArgs := MergeMaps(i.GetDefaultWebArgs(), i.config.HorizonWebParameters) - mergedIngestArgs := MergeMaps(i.GetDefaultIngestArgs(), i.config.HorizonIngestParameters) + mergedWebArgs := MergeMaps(i.getDefaultWebArgs(), i.config.HorizonWebParameters) + mergedIngestArgs := MergeMaps(i.getDefaultIngestArgs(), i.config.HorizonIngestParameters) // Set up Horizon clients i.setupHorizonClient(mergedWebArgs) @@ -431,7 +431,7 @@ func (i *Test) StartHorizon(startIngestProcess bool) error { return nil } -func (i *Test) GetDefaultArgs() map[string]string { +func (i *Test) getDefaultArgs() map[string]string { // TODO: Ideally, we'd be pulling host/port information from the Docker // Compose YAML file itself rather than hardcoding it. return map[string]string{ @@ -449,12 +449,12 @@ func (i *Test) GetDefaultArgs() map[string]string { } } -func (i *Test) GetDefaultWebArgs() map[string]string { - return MergeMaps(i.GetDefaultArgs(), map[string]string{"admin-port": "0"}) +func (i *Test) getDefaultWebArgs() map[string]string { + return MergeMaps(i.getDefaultArgs(), map[string]string{"admin-port": "0"}) } -func (i *Test) GetDefaultIngestArgs() map[string]string { - return MergeMaps(i.GetDefaultArgs(), map[string]string{ +func (i *Test) getDefaultIngestArgs() map[string]string { + return MergeMaps(i.getDefaultArgs(), map[string]string{ "admin-port": strconv.Itoa(i.AdminPort()), "port": "8001", "db-url": i.testDB.DSN, @@ -871,10 +871,7 @@ func (i *Test) WaitForHorizonIngest() { if root.HorizonSequence < 3 || int(root.HorizonSequence) != int(root.IngestSequence) { - //jcart, _ := json.MarshalIndent(root, "", "\t") - - i.t.Logf("Horizon ingesting...") - //i.t.Logf(string(jcart)) + i.t.Logf("Horizon ingesting... %v", root) continue } From e2f95d667c7bc80f1e33f8eb33c42a14ec068631 Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Mon, 25 Nov 2024 11:34:26 -0800 Subject: [PATCH 15/29] Add helpers in intergration tests for creating captive core config --- .../internal/integration/parameters_test.go | 30 ++------ .../internal/test/integration/integration.go | 70 ++++++++++++++----- 2 files changed, 58 insertions(+), 42 deletions(-) diff --git a/services/horizon/internal/integration/parameters_test.go b/services/horizon/internal/integration/parameters_test.go index c7e0d0c75b..e8f55829b8 100644 --- a/services/horizon/internal/integration/parameters_test.go +++ b/services/horizon/internal/integration/parameters_test.go @@ -41,32 +41,10 @@ var networkParamArgs = map[string]string{ horizon.NetworkPassphraseFlagName: "", } -const ( - SimpleCaptiveCoreToml = ` - PEER_PORT=11725 - ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true - - UNSAFE_QUORUM=true - FAILURE_SAFETY=0 - - [[VALIDATORS]] - NAME="local_core" - HOME_DOMAIN="core.local" - PUBLIC_KEY="GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS" - ADDRESS="localhost" - QUALITY="MEDIUM"` - - StellarCoreURL = "http://localhost:11626" -) - -var ( - CaptiveCoreConfigErrMsg = "error generating captive core configuration: invalid config: " -) - // Ensures that BUCKET_DIR_PATH is not an allowed value for Captive Core. func TestBucketDirDisallowed(t *testing.T) { config := `BUCKET_DIR_PATH="/tmp" - ` + SimpleCaptiveCoreToml + ` + integration.SimpleCaptiveCoreToml confName, _, cleanup := createCaptiveCoreConfig(config) defer cleanup() @@ -103,7 +81,7 @@ func TestEnvironmentPreserved(t *testing.T) { testConfig := integration.GetTestConfig() testConfig.HorizonEnvironment = map[string]string{ - "STELLAR_CORE_URL": StellarCoreURL, + "STELLAR_CORE_URL": integration.StellarCoreURL, } test := integration.NewTest(t, *testConfig) @@ -112,7 +90,7 @@ func TestEnvironmentPreserved(t *testing.T) { test.WaitForHorizonIngest() envValue := os.Getenv("STELLAR_CORE_URL") - assert.Equal(t, StellarCoreURL, envValue) + assert.Equal(t, integration.StellarCoreURL, envValue) test.Shutdown() @@ -252,7 +230,7 @@ func TestNetworkEnvironmentVariable(t *testing.T) { // Ensures that the filesystem ends up in the correct state with Captive Core. func TestCaptiveCoreConfigFilesystemState(t *testing.T) { - confName, storagePath, cleanup := createCaptiveCoreConfig(SimpleCaptiveCoreToml) + confName, storagePath, cleanup := createCaptiveCoreConfig(integration.SimpleCaptiveCoreToml) defer cleanup() localParams := integration.MergeMaps(defaultCaptiveCoreParameters, map[string]string{ diff --git a/services/horizon/internal/test/integration/integration.go b/services/horizon/internal/test/integration/integration.go index 107cb33759..a7683c0715 100644 --- a/services/horizon/internal/test/integration/integration.go +++ b/services/horizon/internal/test/integration/integration.go @@ -4,6 +4,7 @@ package integration import ( "context" "fmt" + "github.com/stellar/go/ingest/ledgerbackend" "io/ioutil" "os" "os/exec" @@ -41,13 +42,29 @@ import ( const ( StandaloneNetworkPassphrase = "Standalone Network ; February 2017" - stellarCorePostgresPassword = "mysecretpassword" - horizonDefaultPort = "8000" - adminPort = 6060 - stellarCorePort = 11626 - stellarCorePostgresPort = 5641 - historyArchivePort = 1570 - sorobanRPCPort = 8080 + HorizonDefaultPort = "8000" + AdminPort = 6060 + StellarCorePort = 11626 + HistoryArchivePort = 1570 + SorobanRPCPort = 8080 + HistoryArchiveUrl = "http://localhost:1570" +) + +const ( + SimpleCaptiveCoreToml = ` + PEER_PORT=11725 + ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true + + UNSAFE_QUORUM=true + FAILURE_SAFETY=0 + + [[VALIDATORS]] + NAME="local_core" + HOME_DOMAIN="core.local" + PUBLIC_KEY="GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS" + ADDRESS="localhost" + QUALITY="MEDIUM"` + StellarCoreURL = "http://localhost:11626" ) const HorizonInitErrStr = "cannot initialize Horizon" @@ -163,7 +180,7 @@ func NewTest(t *testing.T, config Config) *Test { } i.prepareShutdownHandlers() - i.coreClient = &stellarcore.Client{URL: "http://localhost:" + strconv.Itoa(stellarCorePort)} + i.coreClient = &stellarcore.Client{URL: "http://localhost:" + strconv.Itoa(StellarCorePort)} if !config.SkipCoreContainerCreation { i.waitForCore() if i.config.EnableSorobanRPC { @@ -436,12 +453,12 @@ func (i *Test) getDefaultArgs() map[string]string { // Compose YAML file itself rather than hardcoding it. return map[string]string{ "ingest": "false", - "history-archive-urls": fmt.Sprintf("http://%s:%d", "localhost", historyArchivePort), + "history-archive-urls": HistoryArchiveUrl, "db-url": i.testDB.RO_DSN, "stellar-core-url": i.coreClient.URL, "network-passphrase": i.passPhrase, "apply-migrations": "true", - "port": horizonDefaultPort, + "port": HorizonDefaultPort, // due to ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING "checkpoint-frequency": "8", "per-hour-rate-limit": "0", // disable rate limiting @@ -537,7 +554,7 @@ func (i *Test) setupHorizonAdminClient(ingestArgs map[string]string) error { func (i *Test) setupHorizonClient(webArgs map[string]string) { hostname := "localhost" - horizonPort := horizonDefaultPort + horizonPort := HorizonDefaultPort if port, ok := webArgs["port"]; ok { horizonPort = port } @@ -547,6 +564,27 @@ func (i *Test) setupHorizonClient(webArgs map[string]string) { } } +func (i *Test) CreateDefaultCaptiveCoreConfig() (*ledgerbackend.CaptiveCoreConfig, error) { + captiveCoreConfig := ledgerbackend.CaptiveCoreConfig{ + BinaryPath: os.Getenv("HORIZON_INTEGRATION_TESTS_CAPTIVE_CORE_BIN"), + HistoryArchiveURLs: []string{HistoryArchiveUrl}, + NetworkPassphrase: StandaloneNetworkPassphrase, + CheckpointFrequency: 8, // This is required for accelerated archive creation for integration test + } + + tomlParams := ledgerbackend.CaptiveCoreTomlParams{ + NetworkPassphrase: StandaloneNetworkPassphrase, + HistoryArchiveURLs: []string{HistoryArchiveUrl}, + } + toml, err := ledgerbackend.NewCaptiveCoreTomlFromData([]byte(SimpleCaptiveCoreToml), tomlParams) + if err != nil { + return nil, err + } + + captiveCoreConfig.Toml = toml + return &captiveCoreConfig, nil +} + const maxWaitForCoreStartup = 30 * time.Second const maxWaitForCoreUpgrade = 5 * time.Second const coreStartupPingInterval = time.Second @@ -604,7 +642,7 @@ func (i *Test) waitForSorobanRPC() { for time.Since(start) < sorobanRPCInitTime { ctx, cancel := context.WithTimeout(context.Background(), sorobanRPCHealthCheckInterval) // TODO: soroban-tools should be exporting a proper Go client - ch := jhttp.NewChannel("http://localhost:"+strconv.Itoa(sorobanRPCPort), nil) + ch := jhttp.NewChannel("http://localhost:"+strconv.Itoa(SorobanRPCPort), nil) sorobanRPCClient := jrpc2.NewClient(ch, nil) callTime := time.Now() _, err := sorobanRPCClient.Call(ctx, "getHealth", nil) @@ -675,7 +713,7 @@ func (i *Test) simulateTransaction( i.syncWithSorobanRPC(uint32(root.HorizonSequence)) // TODO: soroban-tools should be exporting a proper Go client - ch := jhttp.NewChannel("http://localhost:"+strconv.Itoa(sorobanRPCPort), nil) + ch := jhttp.NewChannel("http://localhost:"+strconv.Itoa(SorobanRPCPort), nil) sorobanRPCClient := jrpc2.NewClient(ch, nil) txParams := GetBaseTransactionParamsWithFee(sourceAccount, txnbuild.MinBaseFee, op) txParams.IncrementSequenceNum = false @@ -702,7 +740,7 @@ func (i *Test) syncWithSorobanRPC(ledgerToWaitFor uint32) { result := struct { Sequence uint32 `json:"sequence"` }{} - ch := jhttp.NewChannel("http://localhost:"+strconv.Itoa(sorobanRPCPort), nil) + ch := jhttp.NewChannel("http://localhost:"+strconv.Itoa(SorobanRPCPort), nil) sorobanRPCClient := jrpc2.NewClient(ch, nil) err := sorobanRPCClient.CallResult(context.Background(), "getLatestLedger", nil, &result) assert.NoError(i.t, err) @@ -715,7 +753,7 @@ func (i *Test) syncWithSorobanRPC(ledgerToWaitFor uint32) { } func (i *Test) WaitUntilLedgerEntryTTL(ledgerKey xdr.LedgerKey) { - ch := jhttp.NewChannel("http://localhost:"+strconv.Itoa(sorobanRPCPort), nil) + ch := jhttp.NewChannel("http://localhost:"+strconv.Itoa(SorobanRPCPort), nil) client := jrpc2.NewClient(ch, nil) keyB64, err := xdr.MarshalBase64(ledgerKey) @@ -933,7 +971,7 @@ func (i *Test) StopHorizon() { // AdminPort returns Horizon admin port. func (i *Test) AdminPort() int { - return adminPort + return AdminPort } // Metrics URL returns Horizon metrics URL. From 564c3bb44724d2276681abe45af81ac901e6f7f8 Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Tue, 26 Nov 2024 21:04:36 -0800 Subject: [PATCH 16/29] fix updates to change struct --- ingest/ledger_change_reader.go | 8 ++++---- ingest/ledger_transaction.go | 20 +++++++++---------- .../internal/integration/change_test.go | 1 + 3 files changed, 15 insertions(+), 14 deletions(-) create mode 100644 services/horizon/internal/integration/change_test.go diff --git a/ingest/ledger_change_reader.go b/ingest/ledger_change_reader.go index 0c7bc8e4ec..741edf902a 100644 --- a/ingest/ledger_change_reader.go +++ b/ingest/ledger_change_reader.go @@ -205,10 +205,10 @@ func (r *LedgerChangeReader) Read() (Change, error) { r.LedgerTransactionReader.lcm.UpgradesProcessing()[r.upgradeIndex].Changes, ) ledgerUpgrades := r.LedgerTransactionReader.lcm.UpgradesProcessing() - for _, change := range changes { - change.Reason = Upgrade - change.Ledger = &r.lcm - change.LedgerUpgrade = &ledgerUpgrades[r.upgradeIndex].Upgrade + for i := range changes { + changes[i].Reason = Upgrade + changes[i].Ledger = &r.lcm + changes[i].LedgerUpgrade = &ledgerUpgrades[r.upgradeIndex].Upgrade } r.pending = append(r.pending, changes...) r.upgradeIndex++ diff --git a/ingest/ledger_transaction.go b/ingest/ledger_transaction.go index 3c54cd5764..933e6a38bb 100644 --- a/ingest/ledger_transaction.go +++ b/ingest/ledger_transaction.go @@ -29,18 +29,18 @@ func (t *LedgerTransaction) txInternalError() bool { // connected to fees. func (t *LedgerTransaction) GetFeeChanges() []Change { changes := GetChangesFromLedgerEntryChanges(t.FeeChanges) - for _, change := range changes { - change.Reason = FeeChange - change.Transaction = t + for i := range changes { + changes[i].Reason = FeeChange + changes[i].Transaction = t } return changes } func (t *LedgerTransaction) getTransactionChanges(ledgerEntryChanges xdr.LedgerEntryChanges) []Change { changes := GetChangesFromLedgerEntryChanges(ledgerEntryChanges) - for _, change := range changes { - change.Reason = Transaction - change.Transaction = t + for i := range changes { + changes[i].Reason = Transaction + changes[i].Transaction = t } return changes } @@ -169,10 +169,10 @@ func (t *LedgerTransaction) operationChanges(ops []xdr.OperationMeta, index uint operationMeta := ops[index] changes := GetChangesFromLedgerEntryChanges(operationMeta.Changes) - for _, change := range changes { - change.Reason = Operation - change.Transaction = t - change.OperationIndex = index + for i := range changes { + changes[i].Reason = Operation + changes[i].Transaction = t + changes[i].OperationIndex = index } return changes } diff --git a/services/horizon/internal/integration/change_test.go b/services/horizon/internal/integration/change_test.go new file mode 100644 index 0000000000..76ab1b7282 --- /dev/null +++ b/services/horizon/internal/integration/change_test.go @@ -0,0 +1 @@ +package integration From 88af498aba9919b49b1b7cf2651f7b8a78887a91 Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Tue, 26 Nov 2024 21:45:42 -0800 Subject: [PATCH 17/29] Updates to integration.go --- .../internal/test/integration/integration.go | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/services/horizon/internal/test/integration/integration.go b/services/horizon/internal/test/integration/integration.go index a7683c0715..4ea4be6bef 100644 --- a/services/horizon/internal/test/integration/integration.go +++ b/services/horizon/internal/test/integration/integration.go @@ -564,7 +564,7 @@ func (i *Test) setupHorizonClient(webArgs map[string]string) { } } -func (i *Test) CreateDefaultCaptiveCoreConfig() (*ledgerbackend.CaptiveCoreConfig, error) { +func createDefaultCaptiveCoreConfig() (*ledgerbackend.CaptiveCoreConfig, error) { captiveCoreConfig := ledgerbackend.CaptiveCoreConfig{ BinaryPath: os.Getenv("HORIZON_INTEGRATION_TESTS_CAPTIVE_CORE_BIN"), HistoryArchiveURLs: []string{HistoryArchiveUrl}, @@ -585,6 +585,15 @@ func (i *Test) CreateDefaultCaptiveCoreConfig() (*ledgerbackend.CaptiveCoreConfi return &captiveCoreConfig, nil } +func (i *Test) GetDefaultCaptiveCoreInstance() (*ledgerbackend.CaptiveStellarCore, error) { + ccConfig, err := createDefaultCaptiveCoreConfig() + if err != nil { + return nil, err + } + + return ledgerbackend.NewCaptive(*ccConfig) +} + const maxWaitForCoreStartup = 30 * time.Second const maxWaitForCoreUpgrade = 5 * time.Second const coreStartupPingInterval = time.Second @@ -858,7 +867,7 @@ func (i *Test) RestoreFootprint( } // UpgradeProtocol arms Core with upgrade and blocks until protocol is upgraded. -func (i *Test) UpgradeProtocol(version uint32) { +func (i *Test) UpgradeProtocol(version uint32) int { ctx, cancel := context.WithTimeout(context.Background(), time.Second) err := i.coreClient.Upgrade(ctx, int(version)) cancel() @@ -876,14 +885,17 @@ func (i *Test) UpgradeProtocol(version uint32) { continue } + ledgerSeq := info.Info.Ledger.Num if info.Info.Ledger.Version == int(version) { - i.t.Logf("Protocol upgraded to: %d", info.Info.Ledger.Version) - return + i.t.Logf("Protocol upgraded to: %d, in ledger sequence number: %v, hash: %v", + info.Info.Ledger.Version, ledgerSeq, info.Info.Ledger.Hash) + return ledgerSeq } time.Sleep(time.Second) } i.t.Fatalf("could not upgrade protocol in 10s") + return -1 } func (i *Test) WaitForHorizonWeb() { From 343373b0c9675e4b21b2f8f6289c81d041fe044d Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Tue, 26 Nov 2024 21:45:56 -0800 Subject: [PATCH 18/29] Integration tests for change - part 1 --- .../internal/integration/change_test.go | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/services/horizon/internal/integration/change_test.go b/services/horizon/internal/integration/change_test.go index 76ab1b7282..ceabf6eade 100644 --- a/services/horizon/internal/integration/change_test.go +++ b/services/horizon/internal/integration/change_test.go @@ -1 +1,96 @@ package integration + +import ( + "context" + "github.com/stellar/go/historyarchive" + "github.com/stellar/go/ingest/ledgerbackend" + "github.com/stellar/go/services/horizon/internal/test/integration" + "github.com/stellar/go/txnbuild" + "github.com/stellar/go/xdr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" + "time" +) + +func TestChangeDataForTxWithOneOperation(t *testing.T) { + tt := assert.New(t) + itest := integration.NewTest(t, integration.Config{}) // set config to 21 + master := itest.Master() + + keys, accounts := itest.CreateAccounts(1, "1000") + accAkeys, _ := keys[0], accounts[0] + //accBkeys, accB := keys[1], accounts[1] + + paymentToA := txnbuild.Payment{ + Destination: accAkeys.Address(), + Amount: "100", + Asset: txnbuild.NativeAsset{}, + } + + // Submit a transaction + txResp := itest.MustSubmitOperations(itest.MasterAccount(), master, &paymentToA) + tt.True(txResp.Successful) + //txHash := txResp.Hash + ledgerSeq := uint32(txResp.Ledger) + + // Stop horizon + itest.StopHorizon() + + archive, err := historyarchive.Connect( + itest.GetHorizonIngestConfig().HistoryArchiveURLs[0], + historyarchive.ArchiveOptions{ + NetworkPassphrase: itest.GetHorizonIngestConfig().NetworkPassphrase, + CheckpointFrequency: itest.GetHorizonIngestConfig().CheckpointFrequency, + }) + tt.NoError(err) + + var latestCheckpoint uint32 + publishedNextCheckpoint := func() bool { + has, requestErr := archive.GetRootHAS() + if requestErr != nil { + t.Logf("request to fetch checkpoint failed: %v", requestErr) + return false + } + latestCheckpoint = has.CurrentLedger + return latestCheckpoint > ledgerSeq + } + + // Ensure that a checkpoint has been created with the ledgerNumber you want in it + tt.Eventually(publishedNextCheckpoint, 10*time.Second, time.Second) + + t.Log("---------- STARTING CAPTIVE CORE ---------") + + ledgerSeqToLedgers := getLedgersFromArchive(itest, ledgerSeq) + t.Logf("----- length of hashmap is %v", len(ledgerSeqToLedgers)) +} + +func getLedgersFromArchive(itest *integration.Test, maxLedger uint32) map[uint32]xdr.LedgerCloseMeta { + t := itest.CurrentTest() + captiveCore, err := itest.GetDefaultCaptiveCoreInstance() + defer captiveCore.Close() + + ctx := context.Background() + require.NoError(t, err) + + startingLedger := uint32(2) + + err = captiveCore.PrepareRange(ctx, ledgerbackend.UnboundedRange(startingLedger)) + if err != nil { + t.Fatalf("failed to prepare range: %v", err) + } + + t.Logf("Ledger Range ----- [%v, %v]", startingLedger, maxLedger) + + var seqToLedgersMap = make(map[uint32]xdr.LedgerCloseMeta) + for ledgerSeq := startingLedger; ledgerSeq <= maxLedger; ledgerSeq++ { + ledger, err := captiveCore.GetLedger(ctx, ledgerSeq) + if err != nil { + t.Fatalf("failed to get ledgerNum: %v, error: %v", ledgerSeq, err) + } + seqToLedgersMap[ledgerSeq] = ledger + itest.CurrentTest().Logf("processed ledger ---- %v", ledgerSeq) + } + + return seqToLedgersMap +} From c7f37fbd0ee0fe1e71bde165995fc6d2c630cf8f Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Thu, 28 Nov 2024 10:55:09 -0800 Subject: [PATCH 19/29] Undo all changes to parameters_test.go --- .../internal/integration/core_dump_test.go | 1 + .../internal/integration/parameters_test.go | 30 ++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 services/horizon/internal/integration/core_dump_test.go diff --git a/services/horizon/internal/integration/core_dump_test.go b/services/horizon/internal/integration/core_dump_test.go new file mode 100644 index 0000000000..76ab1b7282 --- /dev/null +++ b/services/horizon/internal/integration/core_dump_test.go @@ -0,0 +1 @@ +package integration diff --git a/services/horizon/internal/integration/parameters_test.go b/services/horizon/internal/integration/parameters_test.go index e8f55829b8..c7e0d0c75b 100644 --- a/services/horizon/internal/integration/parameters_test.go +++ b/services/horizon/internal/integration/parameters_test.go @@ -41,10 +41,32 @@ var networkParamArgs = map[string]string{ horizon.NetworkPassphraseFlagName: "", } +const ( + SimpleCaptiveCoreToml = ` + PEER_PORT=11725 + ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true + + UNSAFE_QUORUM=true + FAILURE_SAFETY=0 + + [[VALIDATORS]] + NAME="local_core" + HOME_DOMAIN="core.local" + PUBLIC_KEY="GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS" + ADDRESS="localhost" + QUALITY="MEDIUM"` + + StellarCoreURL = "http://localhost:11626" +) + +var ( + CaptiveCoreConfigErrMsg = "error generating captive core configuration: invalid config: " +) + // Ensures that BUCKET_DIR_PATH is not an allowed value for Captive Core. func TestBucketDirDisallowed(t *testing.T) { config := `BUCKET_DIR_PATH="/tmp" - ` + integration.SimpleCaptiveCoreToml + ` + SimpleCaptiveCoreToml confName, _, cleanup := createCaptiveCoreConfig(config) defer cleanup() @@ -81,7 +103,7 @@ func TestEnvironmentPreserved(t *testing.T) { testConfig := integration.GetTestConfig() testConfig.HorizonEnvironment = map[string]string{ - "STELLAR_CORE_URL": integration.StellarCoreURL, + "STELLAR_CORE_URL": StellarCoreURL, } test := integration.NewTest(t, *testConfig) @@ -90,7 +112,7 @@ func TestEnvironmentPreserved(t *testing.T) { test.WaitForHorizonIngest() envValue := os.Getenv("STELLAR_CORE_URL") - assert.Equal(t, integration.StellarCoreURL, envValue) + assert.Equal(t, StellarCoreURL, envValue) test.Shutdown() @@ -230,7 +252,7 @@ func TestNetworkEnvironmentVariable(t *testing.T) { // Ensures that the filesystem ends up in the correct state with Captive Core. func TestCaptiveCoreConfigFilesystemState(t *testing.T) { - confName, storagePath, cleanup := createCaptiveCoreConfig(integration.SimpleCaptiveCoreToml) + confName, storagePath, cleanup := createCaptiveCoreConfig(SimpleCaptiveCoreToml) defer cleanup() localParams := integration.MergeMaps(defaultCaptiveCoreParameters, map[string]string{ From 5313aa6e8cb17afe8cb6fe93d234d57aa11fb7fb Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Sat, 30 Nov 2024 11:22:29 -0800 Subject: [PATCH 20/29] Make updates to comments and rename variables --- ingest/change.go | 84 +++++++++++-------- ingest/ledger_change_reader.go | 4 +- ingest/ledger_transaction.go | 8 +- .../internal/integration/change_test.go | 68 +++++++-------- 4 files changed, 84 insertions(+), 80 deletions(-) diff --git a/ingest/change.go b/ingest/change.go index 0e2d72967b..6eb934c982 100644 --- a/ingest/change.go +++ b/ingest/change.go @@ -38,49 +38,61 @@ import ( // the transaction itself and may not be tied to a specific operation within a transaction. // For instance, fees for all operations in a transaction are debited from the source account, // triggering ledger changes without operation-specific details. -// -// Fields: -// -// - Type: The type of the ledger entry being changed -// -// - Pre: The state of the ledger entry before the change. This will be nil if the entry was created. -// -// - Post: The state of the ledger entry after the change. This will be nil if the entry was removed. -// -// - Reason: The reason for the ledger entry change, represented by LedgerEntryChangeReason. -// -// - OperationIdx: The index of the operation in the transaction that caused the change. -// This field is relevant when the change is due to an operation within a transaction -// and won't be used when the change is compacted. -// -// - Tx: A reference to the LedgerTransaction that caused the change. -// Contains information about Transaction, Hash, TxResultPair etc -// -// - Lcm: The LedgerCloseMeta that precipitated the change. -// This is useful only when the Change is caused by an upgrade or by an eviction, i.e. outside a Transaction -// For changes caused by transaction or operations, look at the Tx field -// -// - LedgerUpgrade: Information about the upgrade, if the change occurred as part of an upgrade. type Change struct { - Type xdr.LedgerEntryType - Pre *xdr.LedgerEntry - Post *xdr.LedgerEntry - Reason LedgerEntryChangeReason + // The type of the ledger entry being changed. + Type xdr.LedgerEntryType + + // The state of the LedgerEntry before the change. This will be nil if the entry was created. + Pre *xdr.LedgerEntry + + // The state of the LedgerEntry after the change. This will be nil if the entry was removed. + Post *xdr.LedgerEntry + + // Specifies why the change occurred, represented as a LedgerEntryChangeReason + Reason LedgerEntryChangeReason + + // The index of the operation within the transaction that caused the change. + // This field is relevant only when the Reason is of type LedgerEntryChangeReasonOperation + // This field cannot be relied upon when the compactingChangeReader is used. OperationIndex uint32 - Transaction *LedgerTransaction - Ledger *xdr.LedgerCloseMeta - LedgerUpgrade *xdr.LedgerUpgrade + + // The LedgerTransaction responsible for the change. + // It contains details such as transaction hash, envelope, result pair, and fees. + // This field is populated only when the Reason is one of: + // LedgerEntryChangeReasonTransaction, LedgerEntryChangeReasonOperation or LedgerEntryChangeReasonFee + Transaction *LedgerTransaction + + // The LedgerCloseMeta that precipitated the change. + // This is useful only when the Change is caused by an upgrade or by an eviction, i.e. outside a transaction + // For changes caused by transaction or operations, look at the Transaction field + Ledger *xdr.LedgerCloseMeta + + // Information about the upgrade, if the change occurred as part of an upgrade + // This field is relevant only when the Reason is of type LedgerEntryChangeReasonUpgrade + LedgerUpgrade *xdr.LedgerUpgrade } +// LedgerEntryChangeReason represents the reason for a ledger entry change. type LedgerEntryChangeReason uint16 const ( - Unknown LedgerEntryChangeReason = iota - Operation - Transaction - FeeChange - Upgrade - Eviction + //LedgerEntryChangeReasonUnknown indicates an unknown or unsupported change reason + LedgerEntryChangeReasonUnknown LedgerEntryChangeReason = iota + + //LedgerEntryChangeReasonOperation indicates a change caused by an operation in a transaction + LedgerEntryChangeReasonOperation + + //LedgerEntryChangeReasonTransaction indicates a change caused by the transaction itself + LedgerEntryChangeReasonTransaction + + // LedgerEntryChangeReasonFee indicates a change related to transaction fees. + LedgerEntryChangeReasonFee + + // LedgerEntryChangeReasonUpgrade indicates a change caused by a ledger upgrade. + LedgerEntryChangeReasonUpgrade + + // LedgerEntryChangeReasonEviction indicates a change caused by entry eviction. + LedgerEntryChangeReasonEviction ) // String returns a best effort string representation of the change. diff --git a/ingest/ledger_change_reader.go b/ingest/ledger_change_reader.go index 741edf902a..ff6a5c2792 100644 --- a/ingest/ledger_change_reader.go +++ b/ingest/ledger_change_reader.go @@ -189,7 +189,7 @@ func (r *LedgerChangeReader) Read() (Change, error) { Type: entry.Data.Type, Pre: &entry, Post: nil, - Reason: Eviction, + Reason: LedgerEntryChangeReasonEviction, Ledger: &r.lcm, } } @@ -206,7 +206,7 @@ func (r *LedgerChangeReader) Read() (Change, error) { ) ledgerUpgrades := r.LedgerTransactionReader.lcm.UpgradesProcessing() for i := range changes { - changes[i].Reason = Upgrade + changes[i].Reason = LedgerEntryChangeReasonUpgrade changes[i].Ledger = &r.lcm changes[i].LedgerUpgrade = &ledgerUpgrades[r.upgradeIndex].Upgrade } diff --git a/ingest/ledger_transaction.go b/ingest/ledger_transaction.go index 933e6a38bb..046cff3782 100644 --- a/ingest/ledger_transaction.go +++ b/ingest/ledger_transaction.go @@ -30,7 +30,7 @@ func (t *LedgerTransaction) txInternalError() bool { func (t *LedgerTransaction) GetFeeChanges() []Change { changes := GetChangesFromLedgerEntryChanges(t.FeeChanges) for i := range changes { - changes[i].Reason = FeeChange + changes[i].Reason = LedgerEntryChangeReasonFee changes[i].Transaction = t } return changes @@ -39,7 +39,7 @@ func (t *LedgerTransaction) GetFeeChanges() []Change { func (t *LedgerTransaction) getTransactionChanges(ledgerEntryChanges xdr.LedgerEntryChanges) []Change { changes := GetChangesFromLedgerEntryChanges(ledgerEntryChanges) for i := range changes { - changes[i].Reason = Transaction + changes[i].Reason = LedgerEntryChangeReasonTransaction changes[i].Transaction = t } return changes @@ -163,14 +163,14 @@ func (t *LedgerTransaction) GetOperationChanges(operationIndex uint32) ([]Change func (t *LedgerTransaction) operationChanges(ops []xdr.OperationMeta, index uint32) []Change { if int(index) >= len(ops) { - return []Change{} // TODO - operations_processor somehow seems to be failing without this + return []Change{} } operationMeta := ops[index] changes := GetChangesFromLedgerEntryChanges(operationMeta.Changes) for i := range changes { - changes[i].Reason = Operation + changes[i].Reason = LedgerEntryChangeReasonOperation changes[i].Transaction = t changes[i].OperationIndex = index } diff --git a/services/horizon/internal/integration/change_test.go b/services/horizon/internal/integration/change_test.go index ceabf6eade..2d87fea8ff 100644 --- a/services/horizon/internal/integration/change_test.go +++ b/services/horizon/internal/integration/change_test.go @@ -5,7 +5,6 @@ import ( "github.com/stellar/go/historyarchive" "github.com/stellar/go/ingest/ledgerbackend" "github.com/stellar/go/services/horizon/internal/test/integration" - "github.com/stellar/go/txnbuild" "github.com/stellar/go/xdr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -13,39 +12,19 @@ import ( "time" ) -func TestChangeDataForTxWithOneOperation(t *testing.T) { +func TestCoreDump(t *testing.T) { tt := assert.New(t) - itest := integration.NewTest(t, integration.Config{}) // set config to 21 - master := itest.Master() - - keys, accounts := itest.CreateAccounts(1, "1000") - accAkeys, _ := keys[0], accounts[0] - //accBkeys, accB := keys[1], accounts[1] - - paymentToA := txnbuild.Payment{ - Destination: accAkeys.Address(), - Amount: "100", - Asset: txnbuild.NativeAsset{}, - } - - // Submit a transaction - txResp := itest.MustSubmitOperations(itest.MasterAccount(), master, &paymentToA) - tt.True(txResp.Successful) - //txHash := txResp.Hash - ledgerSeq := uint32(txResp.Ledger) - - // Stop horizon - itest.StopHorizon() - + itest := integration.NewTest(t, integration.Config{SkipHorizonStart: true}) archive, err := historyarchive.Connect( - itest.GetHorizonIngestConfig().HistoryArchiveURLs[0], + integration.HistoryArchiveUrl, historyarchive.ArchiveOptions{ - NetworkPassphrase: itest.GetHorizonIngestConfig().NetworkPassphrase, - CheckpointFrequency: itest.GetHorizonIngestConfig().CheckpointFrequency, + NetworkPassphrase: integration.StandaloneNetworkPassphrase, + CheckpointFrequency: integration.CheckpointFrequency, }) tt.NoError(err) var latestCheckpoint uint32 + startTime := time.Now() publishedNextCheckpoint := func() bool { has, requestErr := archive.GetRootHAS() if requestErr != nil { @@ -53,43 +32,56 @@ func TestChangeDataForTxWithOneOperation(t *testing.T) { return false } latestCheckpoint = has.CurrentLedger - return latestCheckpoint > ledgerSeq + t.Logf("Latest ledger so far: %d", latestCheckpoint) + return latestCheckpoint >= uint32(7) // ALLOW for atleast 3 checkpoints } + //time.Sleep(15 * time.Second) // Ensure that a checkpoint has been created with the ledgerNumber you want in it - tt.Eventually(publishedNextCheckpoint, 10*time.Second, time.Second) + tt.Eventually(publishedNextCheckpoint, 45*time.Second, time.Second) + endTime := time.Now() + t.Logf("waited %v seconds to start captive core...", endTime.Sub(startTime).Seconds()) t.Log("---------- STARTING CAPTIVE CORE ---------") - ledgerSeqToLedgers := getLedgersFromArchive(itest, ledgerSeq) + ledgerSeqToLedgers := getLedgersFromArchive(itest, 2, 7) t.Logf("----- length of hashmap is %v", len(ledgerSeqToLedgers)) + time.Sleep(45 * time.Second) } -func getLedgersFromArchive(itest *integration.Test, maxLedger uint32) map[uint32]xdr.LedgerCloseMeta { +func getLedgersFromArchive(itest *integration.Test, startingLedger uint32, endLedger uint32) map[uint32]xdr.LedgerCloseMeta { t := itest.CurrentTest() - captiveCore, err := itest.GetDefaultCaptiveCoreInstance() + + ccConfig, cleanpupFn, err := itest.CreateCaptiveCoreConfig() + if err != nil { + panic(err) + } + + defer cleanpupFn() + captiveCore, err := ledgerbackend.NewCaptive(*ccConfig) + if err != nil { + panic(err) + } defer captiveCore.Close() ctx := context.Background() require.NoError(t, err) - startingLedger := uint32(2) - - err = captiveCore.PrepareRange(ctx, ledgerbackend.UnboundedRange(startingLedger)) + err = captiveCore.PrepareRange(ctx, ledgerbackend.BoundedRange(startingLedger, endLedger)) if err != nil { t.Fatalf("failed to prepare range: %v", err) } - t.Logf("Ledger Range ----- [%v, %v]", startingLedger, maxLedger) + t.Logf("Ledger Range ----- [%v, %v]", startingLedger, endLedger) var seqToLedgersMap = make(map[uint32]xdr.LedgerCloseMeta) - for ledgerSeq := startingLedger; ledgerSeq <= maxLedger; ledgerSeq++ { + for ledgerSeq := startingLedger; ledgerSeq <= endLedger; ledgerSeq++ { ledger, err := captiveCore.GetLedger(ctx, ledgerSeq) if err != nil { t.Fatalf("failed to get ledgerNum: %v, error: %v", ledgerSeq, err) } seqToLedgersMap[ledgerSeq] = ledger - itest.CurrentTest().Logf("processed ledger ---- %v", ledgerSeq) + itest.CurrentTest().Logf("processed ledgerNum: %v, hash: %v", ledgerSeq, ledger.LedgerHash().HexString()) } return seqToLedgersMap From dccf1e24ca830ea6b8d354eb073da038793fbb2a Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Sat, 30 Nov 2024 11:28:20 -0800 Subject: [PATCH 21/29] - Several changes to integration.go to pull out common functions from parameters_test.go to configure captive core - Add flags to capture state for upgrade version in test. - Add support to skip container deletion for debugging - Add test for upgrade related changes --- .../internal/integration/change_test.go | 73 +++++--- .../internal/integration/core_dump_test.go | 1 - .../internal/integration/parameters_test.go | 55 +----- .../internal/test/integration/integration.go | 159 +++++++++++++----- 4 files changed, 167 insertions(+), 121 deletions(-) delete mode 100644 services/horizon/internal/integration/core_dump_test.go diff --git a/services/horizon/internal/integration/change_test.go b/services/horizon/internal/integration/change_test.go index 2d87fea8ff..cf90a5bba3 100644 --- a/services/horizon/internal/integration/change_test.go +++ b/services/horizon/internal/integration/change_test.go @@ -3,18 +3,19 @@ package integration import ( "context" "github.com/stellar/go/historyarchive" + "github.com/stellar/go/ingest" "github.com/stellar/go/ingest/ledgerbackend" "github.com/stellar/go/services/horizon/internal/test/integration" "github.com/stellar/go/xdr" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "io" "testing" "time" ) -func TestCoreDump(t *testing.T) { +func TestProtocolUpgradeChanges(t *testing.T) { tt := assert.New(t) - itest := integration.NewTest(t, integration.Config{SkipHorizonStart: true}) + itest := integration.NewTest(t, integration.Config{SkipHorizonStart: true, SkipProtocolUpgrade: true}) archive, err := historyarchive.Connect( integration.HistoryArchiveUrl, historyarchive.ArchiveOptions{ @@ -23,8 +24,11 @@ func TestCoreDump(t *testing.T) { }) tt.NoError(err) + // Manually invoke command to upgrade protocol + itest.UpgradeProtocol(itest.Config().ProtocolVersion) + upgradedLedgerSeq, _ := itest.GetUpgradeLedgerSeq() + var latestCheckpoint uint32 - startTime := time.Now() publishedNextCheckpoint := func() bool { has, requestErr := archive.GetRootHAS() if requestErr != nil { @@ -32,48 +36,62 @@ func TestCoreDump(t *testing.T) { return false } latestCheckpoint = has.CurrentLedger - t.Logf("Latest ledger so far: %d", latestCheckpoint) - return latestCheckpoint >= uint32(7) // ALLOW for atleast 3 checkpoints + return latestCheckpoint >= upgradedLedgerSeq } - //time.Sleep(15 * time.Second) - // Ensure that a checkpoint has been created with the ledgerNumber you want in it - tt.Eventually(publishedNextCheckpoint, 45*time.Second, time.Second) - endTime := time.Now() + tt.Eventually(publishedNextCheckpoint, 15*time.Second, time.Second) - t.Logf("waited %v seconds to start captive core...", endTime.Sub(startTime).Seconds()) - t.Log("---------- STARTING CAPTIVE CORE ---------") + prevLedgerToUpgrade := upgradedLedgerSeq - 1 + ledgerSeqToLedgers := getLedgersFromArchive(itest, prevLedgerToUpgrade, upgradedLedgerSeq) + prevLedgerChangeMap := changeMap(getChangesFromLedger(itest, ledgerSeqToLedgers[prevLedgerToUpgrade])) + upgradedLedgerChangeMap := changeMap(getChangesFromLedger(itest, ledgerSeqToLedgers[upgradedLedgerSeq])) + + tt.Zero(prevLedgerChangeMap[ingest.LedgerEntryChangeReasonUpgrade]) + tt.NotZero(upgradedLedgerChangeMap[ingest.LedgerEntryChangeReasonUpgrade]) +} - ledgerSeqToLedgers := getLedgersFromArchive(itest, 2, 7) - t.Logf("----- length of hashmap is %v", len(ledgerSeqToLedgers)) - time.Sleep(45 * time.Second) +func getChangesFromLedger(itest *integration.Test, ledger xdr.LedgerCloseMeta) []ingest.Change { + t := itest.CurrentTest() + changeReader, err := ingest.NewLedgerChangeReaderFromLedgerCloseMeta(itest.GetPassPhrase(), ledger) + changes := make([]ingest.Change, 0) + defer changeReader.Close() + if err != nil { + t.Fatalf("unable to create ledger change reader: %v", err) + } + for { + change, err := changeReader.Read() + if err == io.EOF { + break + } + if err != nil { + t.Fatalf("unable to read ledger change: %v", err) + } + changes = append(changes, change) + } + return changes } func getLedgersFromArchive(itest *integration.Test, startingLedger uint32, endLedger uint32) map[uint32]xdr.LedgerCloseMeta { t := itest.CurrentTest() - ccConfig, cleanpupFn, err := itest.CreateCaptiveCoreConfig() + ccConfig, cleanupFn, err := itest.CreateCaptiveCoreConfig() if err != nil { - panic(err) + t.Fatalf("unable to create captive core config: %v", err) } + defer cleanupFn() - defer cleanpupFn() captiveCore, err := ledgerbackend.NewCaptive(*ccConfig) if err != nil { - panic(err) + t.Fatalf("unable to create captive core: %v", err) } defer captiveCore.Close() ctx := context.Background() - require.NoError(t, err) - err = captiveCore.PrepareRange(ctx, ledgerbackend.BoundedRange(startingLedger, endLedger)) if err != nil { t.Fatalf("failed to prepare range: %v", err) } - t.Logf("Ledger Range ----- [%v, %v]", startingLedger, endLedger) - var seqToLedgersMap = make(map[uint32]xdr.LedgerCloseMeta) for ledgerSeq := startingLedger; ledgerSeq <= endLedger; ledgerSeq++ { ledger, err := captiveCore.GetLedger(ctx, ledgerSeq) @@ -81,8 +99,15 @@ func getLedgersFromArchive(itest *integration.Test, startingLedger uint32, endLe t.Fatalf("failed to get ledgerNum: %v, error: %v", ledgerSeq, err) } seqToLedgersMap[ledgerSeq] = ledger - itest.CurrentTest().Logf("processed ledgerNum: %v, hash: %v", ledgerSeq, ledger.LedgerHash().HexString()) } return seqToLedgersMap } + +func changeMap(changes []ingest.Change) map[ingest.LedgerEntryChangeReason]int { + changeMap := make(map[ingest.LedgerEntryChangeReason]int) + for _, change := range changes { + changeMap[change.Reason]++ + } + return changeMap +} diff --git a/services/horizon/internal/integration/core_dump_test.go b/services/horizon/internal/integration/core_dump_test.go deleted file mode 100644 index 76ab1b7282..0000000000 --- a/services/horizon/internal/integration/core_dump_test.go +++ /dev/null @@ -1 +0,0 @@ -package integration diff --git a/services/horizon/internal/integration/parameters_test.go b/services/horizon/internal/integration/parameters_test.go index c7e0d0c75b..7ded1f9a88 100644 --- a/services/horizon/internal/integration/parameters_test.go +++ b/services/horizon/internal/integration/parameters_test.go @@ -41,24 +41,6 @@ var networkParamArgs = map[string]string{ horizon.NetworkPassphraseFlagName: "", } -const ( - SimpleCaptiveCoreToml = ` - PEER_PORT=11725 - ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true - - UNSAFE_QUORUM=true - FAILURE_SAFETY=0 - - [[VALIDATORS]] - NAME="local_core" - HOME_DOMAIN="core.local" - PUBLIC_KEY="GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS" - ADDRESS="localhost" - QUALITY="MEDIUM"` - - StellarCoreURL = "http://localhost:11626" -) - var ( CaptiveCoreConfigErrMsg = "error generating captive core configuration: invalid config: " ) @@ -66,9 +48,9 @@ var ( // Ensures that BUCKET_DIR_PATH is not an allowed value for Captive Core. func TestBucketDirDisallowed(t *testing.T) { config := `BUCKET_DIR_PATH="/tmp" - ` + SimpleCaptiveCoreToml + ` + integration.SimpleCaptiveCoreToml - confName, _, cleanup := createCaptiveCoreConfig(config) + confName, _, cleanup := integration.CreateCaptiveCoreConfig(config) defer cleanup() testConfig := integration.GetTestConfig() testConfig.HorizonIngestParameters = map[string]string{ @@ -103,7 +85,7 @@ func TestEnvironmentPreserved(t *testing.T) { testConfig := integration.GetTestConfig() testConfig.HorizonEnvironment = map[string]string{ - "STELLAR_CORE_URL": StellarCoreURL, + "STELLAR_CORE_URL": integration.StellarCoreURL, } test := integration.NewTest(t, *testConfig) @@ -112,7 +94,7 @@ func TestEnvironmentPreserved(t *testing.T) { test.WaitForHorizonIngest() envValue := os.Getenv("STELLAR_CORE_URL") - assert.Equal(t, StellarCoreURL, envValue) + assert.Equal(t, integration.StellarCoreURL, envValue) test.Shutdown() @@ -252,7 +234,7 @@ func TestNetworkEnvironmentVariable(t *testing.T) { // Ensures that the filesystem ends up in the correct state with Captive Core. func TestCaptiveCoreConfigFilesystemState(t *testing.T) { - confName, storagePath, cleanup := createCaptiveCoreConfig(SimpleCaptiveCoreToml) + confName, storagePath, cleanup := integration.CreateCaptiveCoreConfig(integration.SimpleCaptiveCoreToml) defer cleanup() localParams := integration.MergeMaps(defaultCaptiveCoreParameters, map[string]string{ @@ -671,30 +653,3 @@ func validateCaptiveCoreDiskState(itest *integration.Test, rootDir string) { tt.DirExists(storageDir) tt.FileExists(coreConf) } - -// createCaptiveCoreConfig will create a temporary TOML config with the -// specified contents as well as a temporary storage directory. You should -// `defer` the returned function to clean these up when you're done. -func createCaptiveCoreConfig(contents string) (string, string, func()) { - tomlFile, err := ioutil.TempFile("", "captive-core-test-*.toml") - defer tomlFile.Close() - if err != nil { - panic(err) - } - - _, err = tomlFile.WriteString(contents) - if err != nil { - panic(err) - } - - storagePath, err := os.MkdirTemp("", "captive-core-test-*-storage") - if err != nil { - panic(err) - } - - filename := tomlFile.Name() - return filename, storagePath, func() { - os.Remove(filename) - os.RemoveAll(storagePath) - } -} diff --git a/services/horizon/internal/test/integration/integration.go b/services/horizon/internal/test/integration/integration.go index 4ea4be6bef..cf283d737c 100644 --- a/services/horizon/internal/test/integration/integration.go +++ b/services/horizon/internal/test/integration/integration.go @@ -48,22 +48,31 @@ const ( HistoryArchivePort = 1570 SorobanRPCPort = 8080 HistoryArchiveUrl = "http://localhost:1570" + CheckpointFrequency = 8 ) const ( SimpleCaptiveCoreToml = ` PEER_PORT=11725 ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING=true - + NETWORK_PASSPHRASE = "Standalone Network ; February 2017" UNSAFE_QUORUM=true FAILURE_SAFETY=0 + RUN_STANDALONE=false + + # Lower the TTL of persistent ledger entries + # so that ledger entry extension/restoring becomes testeable + # These 2 settings need to be present in both places - stellar-core-integration-tests.cfg and here + TESTING_MINIMUM_PERSISTENT_ENTRY_LIFETIME=10 + TESTING_SOROBAN_HIGH_LIMIT_OVERRIDE=true [[VALIDATORS]] NAME="local_core" HOME_DOMAIN="core.local" PUBLIC_KEY="GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS" ADDRESS="localhost" - QUALITY="MEDIUM"` + QUALITY="MEDIUM" +` StellarCoreURL = "http://localhost:11626" ) @@ -73,8 +82,10 @@ type Config struct { ProtocolVersion uint32 EnableSorobanRPC bool SkipCoreContainerCreation bool + SkipCoreContainerDeletion bool // This flag is helpful to debug CoreDockerImage string SorobanRPCDockerImage string + SkipProtocolUpgrade bool // Weird naming here because bools default to false, but we want to start // Horizon by default. @@ -117,13 +128,18 @@ type Test struct { horizonAdminClient *sdk.AdminClient coreClient *stellarcore.Client - webNode *horizon.App - ingestNode *horizon.App - appStopped *sync.WaitGroup - shutdownOnce sync.Once - shutdownCalls []func() - masterKey *keypair.Full - passPhrase string + webNode *horizon.App + ingestNode *horizon.App + appStopped *sync.WaitGroup + shutdownOnce sync.Once + shutdownCalls []func() + masterKey *keypair.Full + passPhrase string + coreUpgradeState *CoreUpgradeState +} + +type CoreUpgradeState struct { + upgradeLedgerSeq uint32 } // GetTestConfig returns the default test Config required to run NewTest. @@ -294,8 +310,13 @@ func (i *Test) prepareShutdownHandlers() { i.ingestNode.Close() } if !i.config.SkipCoreContainerCreation { - i.runComposeCommand("rm", "-fvs", "core") - i.runComposeCommand("rm", "-fvs", "core-postgres") + if !i.config.SkipCoreContainerDeletion { + i.t.Log("Removing core docker containers...") + i.runComposeCommand("rm", "-fvs", "core") + i.runComposeCommand("rm", "-fvs", "core-postgres") + } else { + i.t.Log("Skip core docker container removal for debugging...") + } if i.config.EnableSorobanRPC { i.runComposeCommand("logs", "soroban-rpc") i.runComposeCommand("rm", "-fvs", "soroban-rpc") @@ -358,6 +379,7 @@ func (i *Test) Shutdown() { // StartHorizon initializes and starts the Horizon client-facing API server. // When startIngestProcess=true, start a second process for ingest server func (i *Test) StartHorizon(startIngestProcess bool) error { + i.t.Logf("Starting horizon.....") i.testDB = dbtest.Postgres(i.t) i.shutdownCalls = append(i.shutdownCalls, func() { if i.appStopped == nil { @@ -460,7 +482,7 @@ func (i *Test) getDefaultArgs() map[string]string { "apply-migrations": "true", "port": HorizonDefaultPort, // due to ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING - "checkpoint-frequency": "8", + "checkpoint-frequency": strconv.Itoa(CheckpointFrequency), "per-hour-rate-limit": "0", // disable rate limiting "max-db-connections": "50", // the postgres container supports 100 connections, be conservative } @@ -564,80 +586,115 @@ func (i *Test) setupHorizonClient(webArgs map[string]string) { } } -func createDefaultCaptiveCoreConfig() (*ledgerbackend.CaptiveCoreConfig, error) { +// CreateCaptiveCoreConfig will create a temporary TOML config with the +// specified contents as well as a temporary storage directory. You should +// `defer` the returned function to clean these up when you're done. +func CreateCaptiveCoreConfig(contents string) (string, string, func()) { + tomlFile, err := ioutil.TempFile("", "captive-core-test-*.toml") + defer tomlFile.Close() + if err != nil { + panic(err) + } + + _, err = tomlFile.WriteString(contents) + if err != nil { + panic(err) + } + + storagePath, err := os.MkdirTemp("", "captive-core-test-*-storage") + if err != nil { + panic(err) + } + + filename := tomlFile.Name() + return filename, storagePath, func() { + os.Remove(filename) + os.RemoveAll(storagePath) + } +} + +func (i *Test) CreateCaptiveCoreConfig() (*ledgerbackend.CaptiveCoreConfig, func(), error) { + + confName, storagePath, cleanupFn := CreateCaptiveCoreConfig(SimpleCaptiveCoreToml) + i.t.Logf("Creating Captive Core config files, ConfName: %v, storagePath: %v", confName, storagePath) + captiveCoreConfig := ledgerbackend.CaptiveCoreConfig{ BinaryPath: os.Getenv("HORIZON_INTEGRATION_TESTS_CAPTIVE_CORE_BIN"), HistoryArchiveURLs: []string{HistoryArchiveUrl}, NetworkPassphrase: StandaloneNetworkPassphrase, - CheckpointFrequency: 8, // This is required for accelerated archive creation for integration test + CheckpointFrequency: CheckpointFrequency, // This is required for accelerated archive creation for integration test + UseDB: true, + StoragePath: storagePath, } tomlParams := ledgerbackend.CaptiveCoreTomlParams{ NetworkPassphrase: StandaloneNetworkPassphrase, HistoryArchiveURLs: []string{HistoryArchiveUrl}, + UseDB: true, } + toml, err := ledgerbackend.NewCaptiveCoreTomlFromData([]byte(SimpleCaptiveCoreToml), tomlParams) if err != nil { - return nil, err + return nil, func() {}, err } captiveCoreConfig.Toml = toml - return &captiveCoreConfig, nil -} - -func (i *Test) GetDefaultCaptiveCoreInstance() (*ledgerbackend.CaptiveStellarCore, error) { - ccConfig, err := createDefaultCaptiveCoreConfig() - if err != nil { - return nil, err - } - - return ledgerbackend.NewCaptive(*ccConfig) + return &captiveCoreConfig, cleanupFn, nil } const maxWaitForCoreStartup = 30 * time.Second const maxWaitForCoreUpgrade = 5 * time.Second const coreStartupPingInterval = time.Second -// Wait for core to be up and manually close the first ledger -func (i *Test) waitForCore() { - i.t.Log("Waiting for core to be up...") +// Wait for protocol upgrade +func (i *Test) waitCoreForProtocolUpgrade(protocolVersion uint32) { + i.UpgradeProtocol(protocolVersion) + startTime := time.Now() - for time.Since(startTime) < maxWaitForCoreStartup { + for time.Since(startTime) < maxWaitForCoreUpgrade { ctx, cancel := context.WithTimeout(context.Background(), time.Second) infoTime := time.Now() - _, err := i.coreClient.Info(ctx) + info, err := i.coreClient.Info(ctx) cancel() - if err != nil { - i.t.Logf("could not obtain info response: %v", err) + if err != nil || !info.IsSynced() { + i.t.Logf("Core is still not synced: %v %v", err, info) // sleep up to a second between consecutive calls. if durationSince := time.Since(infoTime); durationSince < coreStartupPingInterval { time.Sleep(coreStartupPingInterval - durationSince) } continue } - break + i.t.Log("Core is up.") + return } + i.t.Fatalf("Core could not sync after %v + %v", maxWaitForCoreStartup, maxWaitForCoreUpgrade) +} - i.UpgradeProtocol(i.config.ProtocolVersion) - - startTime = time.Now() - for time.Since(startTime) < maxWaitForCoreUpgrade { +// Wait for core to be up and manually close the first ledger +func (i *Test) waitForCore() { + i.t.Log("Waiting for core to be up...") + startTime := time.Now() + for time.Since(startTime) < maxWaitForCoreStartup { ctx, cancel := context.WithTimeout(context.Background(), time.Second) infoTime := time.Now() - info, err := i.coreClient.Info(ctx) + _, err := i.coreClient.Info(ctx) cancel() - if err != nil || !info.IsSynced() { - i.t.Logf("Core is still not synced: %v %v", err, info) + if err != nil { + i.t.Logf("could not obtain info response: %v", err) // sleep up to a second between consecutive calls. if durationSince := time.Since(infoTime); durationSince < coreStartupPingInterval { time.Sleep(coreStartupPingInterval - durationSince) } continue } - i.t.Log("Core is up.") - return + break + } + + if !i.config.SkipProtocolUpgrade { + i.waitCoreForProtocolUpgrade(i.config.ProtocolVersion) + } else { + i.t.Log("Core is up. Protocol Upgrade skipped. Please manually upgrade protocol version, if needed...") } - i.t.Fatalf("Core could not sync after %v + %v", maxWaitForCoreStartup, maxWaitForCoreUpgrade) } const sorobanRPCInitTime = 20 * time.Second @@ -867,7 +924,8 @@ func (i *Test) RestoreFootprint( } // UpgradeProtocol arms Core with upgrade and blocks until protocol is upgraded. -func (i *Test) UpgradeProtocol(version uint32) int { +func (i *Test) UpgradeProtocol(version uint32) { + i.t.Logf("Attempting Core Protocol upgade to version: %v", version) ctx, cancel := context.WithTimeout(context.Background(), time.Second) err := i.coreClient.Upgrade(ctx, int(version)) cancel() @@ -889,13 +947,22 @@ func (i *Test) UpgradeProtocol(version uint32) int { if info.Info.Ledger.Version == int(version) { i.t.Logf("Protocol upgraded to: %d, in ledger sequence number: %v, hash: %v", info.Info.Ledger.Version, ledgerSeq, info.Info.Ledger.Hash) - return ledgerSeq + i.coreUpgradeState = &CoreUpgradeState{ + upgradeLedgerSeq: uint32(ledgerSeq), + } + return } time.Sleep(time.Second) } i.t.Fatalf("could not upgrade protocol in 10s") - return -1 +} + +func (i *Test) GetUpgradeLedgerSeq() (uint32, error) { + if i.coreUpgradeState == nil { + return 0, errors.Errorf("Core has not been upgraded yet") + } + return i.coreUpgradeState.upgradeLedgerSeq, nil } func (i *Test) WaitForHorizonWeb() { From 6d970322bb7ae88c8fa4bec684aaaf4d880023ae Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Sat, 30 Nov 2024 11:45:50 -0800 Subject: [PATCH 22/29] cosmetic doc style changes --- ingest/change.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ingest/change.go b/ingest/change.go index 6eb934c982..a45d8fbc27 100644 --- a/ingest/change.go +++ b/ingest/change.go @@ -76,13 +76,13 @@ type Change struct { type LedgerEntryChangeReason uint16 const ( - //LedgerEntryChangeReasonUnknown indicates an unknown or unsupported change reason + // LedgerEntryChangeReasonUnknown indicates an unknown or unsupported change reason LedgerEntryChangeReasonUnknown LedgerEntryChangeReason = iota - //LedgerEntryChangeReasonOperation indicates a change caused by an operation in a transaction + // LedgerEntryChangeReasonOperation indicates a change caused by an operation in a transaction LedgerEntryChangeReasonOperation - //LedgerEntryChangeReasonTransaction indicates a change caused by the transaction itself + // LedgerEntryChangeReasonTransaction indicates a change caused by the transaction itself LedgerEntryChangeReasonTransaction // LedgerEntryChangeReasonFee indicates a change related to transaction fees. From adf96c1861d5749dc65f902f0d1fbd9eaea1586d Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Sat, 30 Nov 2024 12:02:20 -0800 Subject: [PATCH 23/29] check err before file close --- services/horizon/internal/test/integration/integration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/horizon/internal/test/integration/integration.go b/services/horizon/internal/test/integration/integration.go index cf283d737c..30c1f438a4 100644 --- a/services/horizon/internal/test/integration/integration.go +++ b/services/horizon/internal/test/integration/integration.go @@ -591,10 +591,10 @@ func (i *Test) setupHorizonClient(webArgs map[string]string) { // `defer` the returned function to clean these up when you're done. func CreateCaptiveCoreConfig(contents string) (string, string, func()) { tomlFile, err := ioutil.TempFile("", "captive-core-test-*.toml") - defer tomlFile.Close() if err != nil { panic(err) } + defer tomlFile.Close() _, err = tomlFile.WriteString(contents) if err != nil { From fee6685eb9e7d7dd8449f0eefa3ca3215f5048d9 Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Sat, 30 Nov 2024 23:39:20 -0800 Subject: [PATCH 24/29] Add tx test and in change_test.go --- ingest/change.go | 6 +- .../internal/integration/change_test.go | 134 +++++++++++++++--- .../horizon/internal/integration/db_test.go | 22 +-- .../internal/test/integration/integration.go | 10 ++ 4 files changed, 132 insertions(+), 40 deletions(-) diff --git a/ingest/change.go b/ingest/change.go index a45d8fbc27..758a8a11f4 100644 --- a/ingest/change.go +++ b/ingest/change.go @@ -52,7 +52,7 @@ type Change struct { Reason LedgerEntryChangeReason // The index of the operation within the transaction that caused the change. - // This field is relevant only when the Reason is of type LedgerEntryChangeReasonOperation + // This field is relevant only when the Reason is LedgerEntryChangeReasonOperation // This field cannot be relied upon when the compactingChangeReader is used. OperationIndex uint32 @@ -64,11 +64,13 @@ type Change struct { // The LedgerCloseMeta that precipitated the change. // This is useful only when the Change is caused by an upgrade or by an eviction, i.e. outside a transaction + // This field is populated only when the Reason is one of: + // LedgerEntryChangeReasonUpgrade or LedgerEntryChangeReasonEviction // For changes caused by transaction or operations, look at the Transaction field Ledger *xdr.LedgerCloseMeta // Information about the upgrade, if the change occurred as part of an upgrade - // This field is relevant only when the Reason is of type LedgerEntryChangeReasonUpgrade + // This field is relevant only when the Reason is LedgerEntryChangeReasonUpgrade LedgerUpgrade *xdr.LedgerUpgrade } diff --git a/services/horizon/internal/integration/change_test.go b/services/horizon/internal/integration/change_test.go index cf90a5bba3..72f5e2ef2c 100644 --- a/services/horizon/internal/integration/change_test.go +++ b/services/horizon/internal/integration/change_test.go @@ -5,7 +5,9 @@ import ( "github.com/stellar/go/historyarchive" "github.com/stellar/go/ingest" "github.com/stellar/go/ingest/ledgerbackend" + "github.com/stellar/go/keypair" "github.com/stellar/go/services/horizon/internal/test/integration" + "github.com/stellar/go/txnbuild" "github.com/stellar/go/xdr" "github.com/stretchr/testify/assert" "io" @@ -16,38 +18,111 @@ import ( func TestProtocolUpgradeChanges(t *testing.T) { tt := assert.New(t) itest := integration.NewTest(t, integration.Config{SkipHorizonStart: true, SkipProtocolUpgrade: true}) - archive, err := historyarchive.Connect( - integration.HistoryArchiveUrl, - historyarchive.ArchiveOptions{ - NetworkPassphrase: integration.StandaloneNetworkPassphrase, - CheckpointFrequency: integration.CheckpointFrequency, - }) + archive, err := integration.GetHistoryArchive() tt.NoError(err) // Manually invoke command to upgrade protocol itest.UpgradeProtocol(itest.Config().ProtocolVersion) upgradedLedgerSeq, _ := itest.GetUpgradeLedgerSeq() - var latestCheckpoint uint32 - publishedNextCheckpoint := func() bool { - has, requestErr := archive.GetRootHAS() - if requestErr != nil { - t.Logf("request to fetch checkpoint failed: %v", requestErr) - return false - } - latestCheckpoint = has.CurrentLedger - return latestCheckpoint >= upgradedLedgerSeq - } + publishedNextCheckpoint := publishedNextCheckpoint(archive, upgradedLedgerSeq, t) // Ensure that a checkpoint has been created with the ledgerNumber you want in it tt.Eventually(publishedNextCheckpoint, 15*time.Second, time.Second) prevLedgerToUpgrade := upgradedLedgerSeq - 1 ledgerSeqToLedgers := getLedgersFromArchive(itest, prevLedgerToUpgrade, upgradedLedgerSeq) - prevLedgerChangeMap := changeMap(getChangesFromLedger(itest, ledgerSeqToLedgers[prevLedgerToUpgrade])) - upgradedLedgerChangeMap := changeMap(getChangesFromLedger(itest, ledgerSeqToLedgers[upgradedLedgerSeq])) + + prevLedgerChanges := getChangesFromLedger(itest, ledgerSeqToLedgers[prevLedgerToUpgrade]) + prevLedgerChangeMap := changeReasonCountMap(prevLedgerChanges) + upgradedLedgerChanges := getChangesFromLedger(itest, ledgerSeqToLedgers[upgradedLedgerSeq]) + upgradedLedgerChangeMap := changeReasonCountMap(upgradedLedgerChanges) tt.Zero(prevLedgerChangeMap[ingest.LedgerEntryChangeReasonUpgrade]) tt.NotZero(upgradedLedgerChangeMap[ingest.LedgerEntryChangeReasonUpgrade]) + for _, change := range upgradedLedgerChanges { + tt.Equal(change.Ledger.LedgerSequence(), upgradedLedgerSeq) + tt.Empty(change.Transaction) + tt.NotEmpty(change.LedgerUpgrade) + } +} + +func TestOneTxOneOperationChanges(t *testing.T) { + tt := assert.New(t) + itest := integration.NewTest(t, integration.Config{}) + + master := itest.Master() + keys, _ := itest.CreateAccounts(2, "1000") + keyA, keyB := keys[0], keys[1] + + operation := txnbuild.Payment{ + SourceAccount: keyA.Address(), + Destination: keyB.Address(), + Asset: txnbuild.NativeAsset{}, + Amount: "900", + } + txResp, err := itest.SubmitMultiSigOperations(itest.MasterAccount(), []*keypair.Full{master, keyA}, &operation) + if err != nil { + t.Fatalf("failed to submit transaction: %v", err) + } + ledgerSeq := uint32(txResp.Ledger) + + archive, err := integration.GetHistoryArchive() + tt.NoError(err) + + publishedNextCheckpoint := publishedNextCheckpoint(archive, ledgerSeq, t) + // Ensure that a checkpoint has been created with the ledgerNumber you want in it + tt.Eventually(publishedNextCheckpoint, 15*time.Second, time.Second) + + ledger := getLedgersFromArchive(itest, ledgerSeq, ledgerSeq)[ledgerSeq] + changes := getChangesFromLedger(itest, ledger) + + reasonCntMap := changeReasonCountMap(changes) + tt.Equal(2, reasonCntMap[ingest.LedgerEntryChangeReasonOperation]) + tt.Equal(1, reasonCntMap[ingest.LedgerEntryChangeReasonTransaction]) + tt.Equal(1, reasonCntMap[ingest.LedgerEntryChangeReasonFee]) + + reasonToChangeMap := changeReasonToChangeMap(changes) + // Assert Transaction Hash and Ledger Sequence are accurate in all changes + for _, change := range changes { + tt.Equal(change.Transaction.Hash.HexString(), txResp.Hash) + tt.Equal(change.Transaction.Ledger.LedgerSequence(), ledgerSeq) + } + + tt.Equal( + ledgerKey(reasonToChangeMap[ingest.LedgerEntryChangeReasonFee][0]).MustAccount().AccountId.Address(), + master.Address()) + tt.Equal( + ledgerKey(reasonToChangeMap[ingest.LedgerEntryChangeReasonTransaction][0]).MustAccount().AccountId.Address(), + master.Address()) + tt.True(containsAccount(t, reasonToChangeMap[ingest.LedgerEntryChangeReasonOperation], keyA.Address())) + tt.True(containsAccount(t, reasonToChangeMap[ingest.LedgerEntryChangeReasonOperation], keyB.Address())) + tt.False(containsAccount(t, reasonToChangeMap[ingest.LedgerEntryChangeReasonOperation], master.Address())) +} + +// Helper function to check if a specific XX exists in the list +func containsAccount(t *testing.T, slice []ingest.Change, target string) bool { + t.Logf("Target: %v", target) + for _, change := range slice { + addr := ledgerKey(change).MustAccount().AccountId.Address() + t.Logf("Comparing address: %v with target: %v", addr, target) + if addr == target { + return true + } + } + return false +} + +func ledgerKey(c ingest.Change) xdr.LedgerKey { + var l xdr.LedgerKey + var err error + if c.Pre != nil { + l, err = c.Pre.LedgerKey() + } + l, err = c.Post.LedgerKey() + if err != nil { + panic(err) + } + return l } func getChangesFromLedger(itest *integration.Test, ledger xdr.LedgerCloseMeta) []ingest.Change { @@ -104,10 +179,31 @@ func getLedgersFromArchive(itest *integration.Test, startingLedger uint32, endLe return seqToLedgersMap } -func changeMap(changes []ingest.Change) map[ingest.LedgerEntryChangeReason]int { +func changeReasonCountMap(changes []ingest.Change) map[ingest.LedgerEntryChangeReason]int { changeMap := make(map[ingest.LedgerEntryChangeReason]int) for _, change := range changes { changeMap[change.Reason]++ } return changeMap } + +func publishedNextCheckpoint(archive *historyarchive.Archive, ledgerSeq uint32, t *testing.T) func() bool { + return func() bool { + var latestCheckpoint uint32 + has, requestErr := archive.GetRootHAS() + if requestErr != nil { + t.Logf("Request to fetch checkpoint failed: %v", requestErr) + return false + } + latestCheckpoint = has.CurrentLedger + return latestCheckpoint >= ledgerSeq + } +} + +func changeReasonToChangeMap(changes []ingest.Change) map[ingest.LedgerEntryChangeReason][]ingest.Change { + changeMap := make(map[ingest.LedgerEntryChangeReason][]ingest.Change) + for _, change := range changes { + changeMap[change.Reason] = append(changeMap[change.Reason], change) + } + return changeMap +} diff --git a/services/horizon/internal/integration/db_test.go b/services/horizon/internal/integration/db_test.go index 5a2b03e48b..7d85289852 100644 --- a/services/horizon/internal/integration/db_test.go +++ b/services/horizon/internal/integration/db_test.go @@ -15,7 +15,6 @@ import ( "github.com/stretchr/testify/require" "github.com/stellar/go/clients/horizonclient" - "github.com/stellar/go/historyarchive" "github.com/stellar/go/keypair" hProtocol "github.com/stellar/go/protocols/horizon" horizoncmd "github.com/stellar/go/services/horizon/cmd" @@ -507,12 +506,7 @@ func TestReingestDB(t *testing.T) { // cannot ingest past the most recent checkpoint ledger when using captive // core toLedger := uint32(reachedLedger) - archive, err := historyarchive.Connect( - horizonConfig.HistoryArchiveURLs[0], - historyarchive.ArchiveOptions{ - NetworkPassphrase: horizonConfig.NetworkPassphrase, - CheckpointFrequency: horizonConfig.CheckpointFrequency, - }) + archive, err := integration.GetHistoryArchive() tt.NoError(err) // make sure a full checkpoint has elapsed otherwise there will be nothing to reingest @@ -635,12 +629,7 @@ func TestReingestDBWithFilterRules(t *testing.T) { itest, _ := initializeDBIntegrationTest(t) tt := assert.New(t) - archive, err := historyarchive.Connect( - itest.GetHorizonIngestConfig().HistoryArchiveURLs[0], - historyarchive.ArchiveOptions{ - NetworkPassphrase: itest.GetHorizonIngestConfig().NetworkPassphrase, - CheckpointFrequency: itest.GetHorizonIngestConfig().CheckpointFrequency, - }) + archive, err := integration.GetHistoryArchive() tt.NoError(err) // make sure one full checkpoint has elapsed before making ledger entries @@ -879,12 +868,7 @@ func TestFillGaps(t *testing.T) { // cap reachedLedger to the nearest checkpoint ledger because reingest range cannot ingest past the most // recent checkpoint ledger when using captive core toLedger := uint32(reachedLedger) - archive, err := historyarchive.Connect( - horizonConfig.HistoryArchiveURLs[0], - historyarchive.ArchiveOptions{ - NetworkPassphrase: horizonConfig.NetworkPassphrase, - CheckpointFrequency: horizonConfig.CheckpointFrequency, - }) + archive, err := integration.GetHistoryArchive() tt.NoError(err) t.Run("validate parallel range", func(t *testing.T) { diff --git a/services/horizon/internal/test/integration/integration.go b/services/horizon/internal/test/integration/integration.go index 30c1f438a4..7846fa7337 100644 --- a/services/horizon/internal/test/integration/integration.go +++ b/services/horizon/internal/test/integration/integration.go @@ -4,6 +4,7 @@ package integration import ( "context" "fmt" + "github.com/stellar/go/historyarchive" "github.com/stellar/go/ingest/ledgerbackend" "io/ioutil" "os" @@ -1492,3 +1493,12 @@ func GetCoreMaxSupportedProtocol() uint32 { func (i *Test) GetEffectiveProtocolVersion() uint32 { return i.config.ProtocolVersion } + +func GetHistoryArchive() (*historyarchive.Archive, error) { + return historyarchive.Connect( + HistoryArchiveUrl, + historyarchive.ArchiveOptions{ + NetworkPassphrase: StandaloneNetworkPassphrase, + CheckpointFrequency: CheckpointFrequency, + }) +} From 157c12edc167523c6713356d45b0ded4113a8b91 Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Sun, 1 Dec 2024 00:01:53 -0800 Subject: [PATCH 25/29] code cleanup --- .../internal/integration/change_test.go | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/services/horizon/internal/integration/change_test.go b/services/horizon/internal/integration/change_test.go index 72f5e2ef2c..2b87e2171c 100644 --- a/services/horizon/internal/integration/change_test.go +++ b/services/horizon/internal/integration/change_test.go @@ -61,9 +61,7 @@ func TestOneTxOneOperationChanges(t *testing.T) { Amount: "900", } txResp, err := itest.SubmitMultiSigOperations(itest.MasterAccount(), []*keypair.Full{master, keyA}, &operation) - if err != nil { - t.Fatalf("failed to submit transaction: %v", err) - } + tt.NoError(err) ledgerSeq := uint32(txResp.Ledger) archive, err := integration.GetHistoryArchive() @@ -94,17 +92,16 @@ func TestOneTxOneOperationChanges(t *testing.T) { tt.Equal( ledgerKey(reasonToChangeMap[ingest.LedgerEntryChangeReasonTransaction][0]).MustAccount().AccountId.Address(), master.Address()) - tt.True(containsAccount(t, reasonToChangeMap[ingest.LedgerEntryChangeReasonOperation], keyA.Address())) - tt.True(containsAccount(t, reasonToChangeMap[ingest.LedgerEntryChangeReasonOperation], keyB.Address())) - tt.False(containsAccount(t, reasonToChangeMap[ingest.LedgerEntryChangeReasonOperation], master.Address())) + tt.True(containsAccount(reasonToChangeMap[ingest.LedgerEntryChangeReasonOperation], keyA.Address())) + tt.True(containsAccount(reasonToChangeMap[ingest.LedgerEntryChangeReasonOperation], keyB.Address())) + // MasterAccount shouldnt show up in operation level changes + tt.False(containsAccount(reasonToChangeMap[ingest.LedgerEntryChangeReasonOperation], master.Address())) } // Helper function to check if a specific XX exists in the list -func containsAccount(t *testing.T, slice []ingest.Change, target string) bool { - t.Logf("Target: %v", target) +func containsAccount(slice []ingest.Change, target string) bool { for _, change := range slice { addr := ledgerKey(change).MustAccount().AccountId.Address() - t.Logf("Comparing address: %v with target: %v", addr, target) if addr == target { return true } @@ -114,14 +111,10 @@ func containsAccount(t *testing.T, slice []ingest.Change, target string) bool { func ledgerKey(c ingest.Change) xdr.LedgerKey { var l xdr.LedgerKey - var err error if c.Pre != nil { - l, err = c.Pre.LedgerKey() - } - l, err = c.Post.LedgerKey() - if err != nil { - panic(err) + l, _ = c.Pre.LedgerKey() } + l, _ = c.Post.LedgerKey() return l } From b82a9dd16b34ce1d595c1c2feb473e61ecfa35da Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Mon, 2 Dec 2024 16:26:09 -0800 Subject: [PATCH 26/29] Rework test fixtures --- .../internal/integration/change_test.go | 120 ++++++++++++------ .../internal/test/integration/integration.go | 69 +++++----- 2 files changed, 110 insertions(+), 79 deletions(-) diff --git a/services/horizon/internal/integration/change_test.go b/services/horizon/internal/integration/change_test.go index 2b87e2171c..f9fcce0ff4 100644 --- a/services/horizon/internal/integration/change_test.go +++ b/services/horizon/internal/integration/change_test.go @@ -2,7 +2,6 @@ package integration import ( "context" - "github.com/stellar/go/historyarchive" "github.com/stellar/go/ingest" "github.com/stellar/go/ingest/ledgerbackend" "github.com/stellar/go/keypair" @@ -11,26 +10,24 @@ import ( "github.com/stellar/go/xdr" "github.com/stretchr/testify/assert" "io" + "sort" "testing" "time" ) func TestProtocolUpgradeChanges(t *testing.T) { tt := assert.New(t) - itest := integration.NewTest(t, integration.Config{SkipHorizonStart: true, SkipProtocolUpgrade: true}) - archive, err := integration.GetHistoryArchive() - tt.NoError(err) + itest := integration.NewTest(t, integration.Config{SkipHorizonStart: true}) - // Manually invoke command to upgrade protocol - itest.UpgradeProtocol(itest.Config().ProtocolVersion) - upgradedLedgerSeq, _ := itest.GetUpgradeLedgerSeq() + upgradedLedgerAppx, _ := itest.GetUpgradedLedgerSeqAppx() + waitForLedgerInArchive(t, 15*time.Second, upgradedLedgerAppx) - publishedNextCheckpoint := publishedNextCheckpoint(archive, upgradedLedgerSeq, t) - // Ensure that a checkpoint has been created with the ledgerNumber you want in it - tt.Eventually(publishedNextCheckpoint, 15*time.Second, time.Second) + ledgerSeqToLedgers := getLedgers(itest, 2, upgradedLedgerAppx) + // It is important to find the "exact" ledger which is representative of protocol upgrade + // and the one before it, to check for upgrade related changes + upgradedLedgerSeq := getExactUpgradedLedgerSeq(ledgerSeqToLedgers, itest.Config().ProtocolVersion) prevLedgerToUpgrade := upgradedLedgerSeq - 1 - ledgerSeqToLedgers := getLedgersFromArchive(itest, prevLedgerToUpgrade, upgradedLedgerSeq) prevLedgerChanges := getChangesFromLedger(itest, ledgerSeqToLedgers[prevLedgerToUpgrade]) prevLedgerChangeMap := changeReasonCountMap(prevLedgerChanges) @@ -52,26 +49,21 @@ func TestOneTxOneOperationChanges(t *testing.T) { master := itest.Master() keys, _ := itest.CreateAccounts(2, "1000") - keyA, keyB := keys[0], keys[1] + srcAcc, destAcc := keys[0], keys[1] operation := txnbuild.Payment{ - SourceAccount: keyA.Address(), - Destination: keyB.Address(), + SourceAccount: srcAcc.Address(), + Destination: destAcc.Address(), Asset: txnbuild.NativeAsset{}, Amount: "900", } - txResp, err := itest.SubmitMultiSigOperations(itest.MasterAccount(), []*keypair.Full{master, keyA}, &operation) - tt.NoError(err) - ledgerSeq := uint32(txResp.Ledger) - - archive, err := integration.GetHistoryArchive() + txResp, err := itest.SubmitMultiSigOperations(itest.MasterAccount(), []*keypair.Full{master, srcAcc}, &operation) tt.NoError(err) - publishedNextCheckpoint := publishedNextCheckpoint(archive, ledgerSeq, t) - // Ensure that a checkpoint has been created with the ledgerNumber you want in it - tt.Eventually(publishedNextCheckpoint, 15*time.Second, time.Second) + ledgerSeq := uint32(txResp.Ledger) + waitForLedgerInArchive(t, 15*time.Second, ledgerSeq) - ledger := getLedgersFromArchive(itest, ledgerSeq, ledgerSeq)[ledgerSeq] + ledger := getLedgers(itest, ledgerSeq, ledgerSeq)[ledgerSeq] changes := getChangesFromLedger(itest, ledger) reasonCntMap := changeReasonCountMap(changes) @@ -86,16 +78,40 @@ func TestOneTxOneOperationChanges(t *testing.T) { tt.Equal(change.Transaction.Ledger.LedgerSequence(), ledgerSeq) } - tt.Equal( - ledgerKey(reasonToChangeMap[ingest.LedgerEntryChangeReasonFee][0]).MustAccount().AccountId.Address(), - master.Address()) - tt.Equal( - ledgerKey(reasonToChangeMap[ingest.LedgerEntryChangeReasonTransaction][0]).MustAccount().AccountId.Address(), - master.Address()) - tt.True(containsAccount(reasonToChangeMap[ingest.LedgerEntryChangeReasonOperation], keyA.Address())) - tt.True(containsAccount(reasonToChangeMap[ingest.LedgerEntryChangeReasonOperation], keyB.Address())) + accountFromEntry := func(e *xdr.LedgerEntry) xdr.AccountEntry { + return e.Data.MustAccount() + } + + changeForAccount := func(changes []ingest.Change, target string) ingest.Change { + for _, change := range changes { + acc := change.Pre.Data.MustAccount() + if acc.AccountId.Address() == target { + return change + } + } + return ingest.Change{} + } + + feeRelatedChange := reasonToChangeMap[ingest.LedgerEntryChangeReasonFee][0] + txRelatedChange := reasonToChangeMap[ingest.LedgerEntryChangeReasonTransaction][0] + operationChanges := reasonToChangeMap[ingest.LedgerEntryChangeReasonOperation] + + tt.Equal(accountFromEntry(feeRelatedChange.Pre).AccountId.Address(), master.Address()) + tt.Equal(accountFromEntry(txRelatedChange.Pre).AccountId.Address(), master.Address()) + tt.True(containsAccount(operationChanges, srcAcc.Address())) + tt.True(containsAccount(operationChanges, destAcc.Address())) // MasterAccount shouldnt show up in operation level changes - tt.False(containsAccount(reasonToChangeMap[ingest.LedgerEntryChangeReasonOperation], master.Address())) + tt.False(containsAccount(operationChanges, master.Address())) + + tt.True(accountFromEntry(feeRelatedChange.Pre).Balance > accountFromEntry(feeRelatedChange.Post).Balance) + tt.True(accountFromEntry(txRelatedChange.Pre).SeqNum < accountFromEntry(txRelatedChange.Post).SeqNum) + + srcAccChange := changeForAccount(operationChanges, srcAcc.Address()) + destAccChange := changeForAccount(operationChanges, destAcc.Address()) + + tt.True(accountFromEntry(srcAccChange.Pre).Balance < accountFromEntry(srcAccChange.Post).Balance) + tt.True(accountFromEntry(destAccChange.Pre).Balance > accountFromEntry(destAccChange.Post).Balance) + } // Helper function to check if a specific XX exists in the list @@ -139,7 +155,7 @@ func getChangesFromLedger(itest *integration.Test, ledger xdr.LedgerCloseMeta) [ return changes } -func getLedgersFromArchive(itest *integration.Test, startingLedger uint32, endLedger uint32) map[uint32]xdr.LedgerCloseMeta { +func getLedgers(itest *integration.Test, startingLedger uint32, endLedger uint32) map[uint32]xdr.LedgerCloseMeta { t := itest.CurrentTest() ccConfig, cleanupFn, err := itest.CreateCaptiveCoreConfig() @@ -180,9 +196,22 @@ func changeReasonCountMap(changes []ingest.Change) map[ingest.LedgerEntryChangeR return changeMap } -func publishedNextCheckpoint(archive *historyarchive.Archive, ledgerSeq uint32, t *testing.T) func() bool { - return func() bool { - var latestCheckpoint uint32 +func changeReasonToChangeMap(changes []ingest.Change) map[ingest.LedgerEntryChangeReason][]ingest.Change { + changeMap := make(map[ingest.LedgerEntryChangeReason][]ingest.Change) + for _, change := range changes { + changeMap[change.Reason] = append(changeMap[change.Reason], change) + } + return changeMap +} + +func waitForLedgerInArchive(t *testing.T, waitTime time.Duration, ledgerSeq uint32) { + archive, err := integration.GetHistoryArchive() + if err != nil { + t.Fatalf("could not get history archive: %v", err) + } + + var latestCheckpoint uint32 + var f = func() bool { has, requestErr := archive.GetRootHAS() if requestErr != nil { t.Logf("Request to fetch checkpoint failed: %v", requestErr) @@ -191,12 +220,21 @@ func publishedNextCheckpoint(archive *historyarchive.Archive, ledgerSeq uint32, latestCheckpoint = has.CurrentLedger return latestCheckpoint >= ledgerSeq } + + assert.Eventually(t, f, waitTime, 1*time.Second) } -func changeReasonToChangeMap(changes []ingest.Change) map[ingest.LedgerEntryChangeReason][]ingest.Change { - changeMap := make(map[ingest.LedgerEntryChangeReason][]ingest.Change) - for _, change := range changes { - changeMap[change.Reason] = append(changeMap[change.Reason], change) +func getExactUpgradedLedgerSeq(ledgerMap map[uint32]xdr.LedgerCloseMeta, version uint32) uint32 { + keys := make([]int, 0, len(ledgerMap)) + for key, _ := range ledgerMap { + keys = append(keys, int(key)) } - return changeMap + sort.Ints(keys) + + for _, key := range keys { + if ledgerMap[uint32(key)].ProtocolVersion() == version { + return uint32(key) + } + } + return 0 } diff --git a/services/horizon/internal/test/integration/integration.go b/services/horizon/internal/test/integration/integration.go index 7846fa7337..3002e77be6 100644 --- a/services/horizon/internal/test/integration/integration.go +++ b/services/horizon/internal/test/integration/integration.go @@ -140,7 +140,7 @@ type Test struct { } type CoreUpgradeState struct { - upgradeLedgerSeq uint32 + maxUpgradeLedger uint32 } // GetTestConfig returns the default test Config required to run NewTest. @@ -647,55 +647,46 @@ const maxWaitForCoreStartup = 30 * time.Second const maxWaitForCoreUpgrade = 5 * time.Second const coreStartupPingInterval = time.Second -// Wait for protocol upgrade -func (i *Test) waitCoreForProtocolUpgrade(protocolVersion uint32) { - i.UpgradeProtocol(protocolVersion) - +// Wait for core to be up and manually close the first ledger +func (i *Test) waitForCore() { + i.t.Log("Waiting for core to be up...") startTime := time.Now() - for time.Since(startTime) < maxWaitForCoreUpgrade { + for time.Since(startTime) < maxWaitForCoreStartup { ctx, cancel := context.WithTimeout(context.Background(), time.Second) infoTime := time.Now() - info, err := i.coreClient.Info(ctx) + _, err := i.coreClient.Info(ctx) cancel() - if err != nil || !info.IsSynced() { - i.t.Logf("Core is still not synced: %v %v", err, info) + if err != nil { + i.t.Logf("could not obtain info response: %v", err) // sleep up to a second between consecutive calls. if durationSince := time.Since(infoTime); durationSince < coreStartupPingInterval { time.Sleep(coreStartupPingInterval - durationSince) } continue } - i.t.Log("Core is up.") - return + break } - i.t.Fatalf("Core could not sync after %v + %v", maxWaitForCoreStartup, maxWaitForCoreUpgrade) -} -// Wait for core to be up and manually close the first ledger -func (i *Test) waitForCore() { - i.t.Log("Waiting for core to be up...") - startTime := time.Now() - for time.Since(startTime) < maxWaitForCoreStartup { + i.UpgradeProtocol(i.config.ProtocolVersion) + + startTime = time.Now() + for time.Since(startTime) < maxWaitForCoreUpgrade { ctx, cancel := context.WithTimeout(context.Background(), time.Second) infoTime := time.Now() - _, err := i.coreClient.Info(ctx) + info, err := i.coreClient.Info(ctx) cancel() - if err != nil { - i.t.Logf("could not obtain info response: %v", err) + if err != nil || !info.IsSynced() { + i.t.Logf("Core is still not synced: %v %v", err, info) // sleep up to a second between consecutive calls. if durationSince := time.Since(infoTime); durationSince < coreStartupPingInterval { time.Sleep(coreStartupPingInterval - durationSince) } continue } - break - } - - if !i.config.SkipProtocolUpgrade { - i.waitCoreForProtocolUpgrade(i.config.ProtocolVersion) - } else { - i.t.Log("Core is up. Protocol Upgrade skipped. Please manually upgrade protocol version, if needed...") + i.t.Log("Core is up.") + return } + i.t.Fatalf("Core could not sync after %v + %v", maxWaitForCoreStartup, maxWaitForCoreUpgrade) } const sorobanRPCInitTime = 20 * time.Second @@ -948,9 +939,10 @@ func (i *Test) UpgradeProtocol(version uint32) { if info.Info.Ledger.Version == int(version) { i.t.Logf("Protocol upgraded to: %d, in ledger sequence number: %v, hash: %v", info.Info.Ledger.Version, ledgerSeq, info.Info.Ledger.Hash) - i.coreUpgradeState = &CoreUpgradeState{ - upgradeLedgerSeq: uint32(ledgerSeq), - } + // Mark the fact that the core has been upgraded as of this ledger sequence + // It could have been earlier than this, but certainly no later. + // The core upgrade could have happened in any ledger since the coreClient.Upgrade was issued + i.coreUpgradeState = &CoreUpgradeState{maxUpgradeLedger: uint32(ledgerSeq)} return } time.Sleep(time.Second) @@ -959,13 +951,6 @@ func (i *Test) UpgradeProtocol(version uint32) { i.t.Fatalf("could not upgrade protocol in 10s") } -func (i *Test) GetUpgradeLedgerSeq() (uint32, error) { - if i.coreUpgradeState == nil { - return 0, errors.Errorf("Core has not been upgraded yet") - } - return i.coreUpgradeState.upgradeLedgerSeq, nil -} - func (i *Test) WaitForHorizonWeb() { // wait until the web server is up before continuing to test requests require.Eventually(i.t, func() bool { @@ -1502,3 +1487,11 @@ func GetHistoryArchive() (*historyarchive.Archive, error) { CheckpointFrequency: CheckpointFrequency, }) } + +// This is approximate becuase the upgrade could have happened at a leger before this as well. +func (i *Test) GetUpgradedLedgerSeqAppx() (uint32, error) { + if i.coreUpgradeState == nil { + return 0, errors.Errorf("Core has not been upgraded yet") + } + return i.coreUpgradeState.maxUpgradeLedger, nil +} From 3bd80ecca352e0c7238dc6b502e6e5c7cd6078f6 Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Mon, 2 Dec 2024 19:50:35 -0800 Subject: [PATCH 27/29] reformat file --- services/horizon/internal/integration/change_test.go | 2 +- services/horizon/internal/test/integration/integration.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/horizon/internal/integration/change_test.go b/services/horizon/internal/integration/change_test.go index f9fcce0ff4..9bd7dec5a5 100644 --- a/services/horizon/internal/integration/change_test.go +++ b/services/horizon/internal/integration/change_test.go @@ -226,7 +226,7 @@ func waitForLedgerInArchive(t *testing.T, waitTime time.Duration, ledgerSeq uint func getExactUpgradedLedgerSeq(ledgerMap map[uint32]xdr.LedgerCloseMeta, version uint32) uint32 { keys := make([]int, 0, len(ledgerMap)) - for key, _ := range ledgerMap { + for key := range ledgerMap { keys = append(keys, int(key)) } sort.Ints(keys) diff --git a/services/horizon/internal/test/integration/integration.go b/services/horizon/internal/test/integration/integration.go index 3002e77be6..5d2b368afa 100644 --- a/services/horizon/internal/test/integration/integration.go +++ b/services/horizon/internal/test/integration/integration.go @@ -620,7 +620,7 @@ func (i *Test) CreateCaptiveCoreConfig() (*ledgerbackend.CaptiveCoreConfig, func i.t.Logf("Creating Captive Core config files, ConfName: %v, storagePath: %v", confName, storagePath) captiveCoreConfig := ledgerbackend.CaptiveCoreConfig{ - BinaryPath: os.Getenv("HORIZON_INTEGRATION_TESTS_CAPTIVE_CORE_BIN"), + BinaryPath: i.coreConfig.binaryPath, HistoryArchiveURLs: []string{HistoryArchiveUrl}, NetworkPassphrase: StandaloneNetworkPassphrase, CheckpointFrequency: CheckpointFrequency, // This is required for accelerated archive creation for integration test From e4b3a67f9e03d68c84eae6e9cbea69e0e13679bc Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Mon, 2 Dec 2024 22:15:08 -0800 Subject: [PATCH 28/29] Fix breaking test --- .../internal/integration/change_test.go | 51 ++++++++----------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/services/horizon/internal/integration/change_test.go b/services/horizon/internal/integration/change_test.go index 9bd7dec5a5..e533408a06 100644 --- a/services/horizon/internal/integration/change_test.go +++ b/services/horizon/internal/integration/change_test.go @@ -72,12 +72,18 @@ func TestOneTxOneOperationChanges(t *testing.T) { tt.Equal(1, reasonCntMap[ingest.LedgerEntryChangeReasonFee]) reasonToChangeMap := changeReasonToChangeMap(changes) - // Assert Transaction Hash and Ledger Sequence are accurate in all changes + // Assert Transaction Hash and Ledger Sequence within Transaction are accurate in all changes for _, change := range changes { tt.Equal(change.Transaction.Hash.HexString(), txResp.Hash) tt.Equal(change.Transaction.Ledger.LedgerSequence(), ledgerSeq) + tt.Empty(change.Ledger) + tt.Empty(change.LedgerUpgrade) } + feeRelatedChange := reasonToChangeMap[ingest.LedgerEntryChangeReasonFee][0] + txRelatedChange := reasonToChangeMap[ingest.LedgerEntryChangeReasonTransaction][0] + operationChanges := reasonToChangeMap[ingest.LedgerEntryChangeReasonOperation] + accountFromEntry := func(e *xdr.LedgerEntry) xdr.AccountEntry { return e.Data.MustAccount() } @@ -92,46 +98,29 @@ func TestOneTxOneOperationChanges(t *testing.T) { return ingest.Change{} } - feeRelatedChange := reasonToChangeMap[ingest.LedgerEntryChangeReasonFee][0] - txRelatedChange := reasonToChangeMap[ingest.LedgerEntryChangeReasonTransaction][0] - operationChanges := reasonToChangeMap[ingest.LedgerEntryChangeReasonOperation] + containsAccount := func(changes []ingest.Change, target string) bool { + for _, change := range changes { + addr := change.Pre.Data.MustAccount().AccountId.Address() + if addr == target { + return true + } + } + return false + } tt.Equal(accountFromEntry(feeRelatedChange.Pre).AccountId.Address(), master.Address()) tt.Equal(accountFromEntry(txRelatedChange.Pre).AccountId.Address(), master.Address()) tt.True(containsAccount(operationChanges, srcAcc.Address())) tt.True(containsAccount(operationChanges, destAcc.Address())) - // MasterAccount shouldnt show up in operation level changes + // MasterAccount shouldn't show up in operation level changes tt.False(containsAccount(operationChanges, master.Address())) - tt.True(accountFromEntry(feeRelatedChange.Pre).Balance > accountFromEntry(feeRelatedChange.Post).Balance) - tt.True(accountFromEntry(txRelatedChange.Pre).SeqNum < accountFromEntry(txRelatedChange.Post).SeqNum) + tt.True(accountFromEntry(txRelatedChange.Post).SeqNum == accountFromEntry(txRelatedChange.Pre).SeqNum+1) srcAccChange := changeForAccount(operationChanges, srcAcc.Address()) destAccChange := changeForAccount(operationChanges, destAcc.Address()) - - tt.True(accountFromEntry(srcAccChange.Pre).Balance < accountFromEntry(srcAccChange.Post).Balance) - tt.True(accountFromEntry(destAccChange.Pre).Balance > accountFromEntry(destAccChange.Post).Balance) - -} - -// Helper function to check if a specific XX exists in the list -func containsAccount(slice []ingest.Change, target string) bool { - for _, change := range slice { - addr := ledgerKey(change).MustAccount().AccountId.Address() - if addr == target { - return true - } - } - return false -} - -func ledgerKey(c ingest.Change) xdr.LedgerKey { - var l xdr.LedgerKey - if c.Pre != nil { - l, _ = c.Pre.LedgerKey() - } - l, _ = c.Post.LedgerKey() - return l + tt.True(accountFromEntry(srcAccChange.Pre).Balance > accountFromEntry(srcAccChange.Post).Balance) + tt.True(accountFromEntry(destAccChange.Pre).Balance < accountFromEntry(destAccChange.Post).Balance) } func getChangesFromLedger(itest *integration.Test, ledger xdr.LedgerCloseMeta) []ingest.Change { From c3fbe2490aac24a6e5375660ee9443d46490a3b6 Mon Sep 17 00:00:00 2001 From: Karthik Iyer Date: Tue, 3 Dec 2024 09:14:52 -0800 Subject: [PATCH 29/29] Address code review comments --- .../internal/integration/change_test.go | 26 ++++++++++--------- .../internal/test/integration/integration.go | 8 +++--- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/services/horizon/internal/integration/change_test.go b/services/horizon/internal/integration/change_test.go index e533408a06..bf2df266eb 100644 --- a/services/horizon/internal/integration/change_test.go +++ b/services/horizon/internal/integration/change_test.go @@ -147,11 +147,10 @@ func getChangesFromLedger(itest *integration.Test, ledger xdr.LedgerCloseMeta) [ func getLedgers(itest *integration.Test, startingLedger uint32, endLedger uint32) map[uint32]xdr.LedgerCloseMeta { t := itest.CurrentTest() - ccConfig, cleanupFn, err := itest.CreateCaptiveCoreConfig() + ccConfig, err := itest.CreateCaptiveCoreConfig() if err != nil { t.Fatalf("unable to create captive core config: %v", err) } - defer cleanupFn() captiveCore, err := ledgerbackend.NewCaptive(*ccConfig) if err != nil { @@ -200,17 +199,20 @@ func waitForLedgerInArchive(t *testing.T, waitTime time.Duration, ledgerSeq uint } var latestCheckpoint uint32 - var f = func() bool { - has, requestErr := archive.GetRootHAS() - if requestErr != nil { - t.Logf("Request to fetch checkpoint failed: %v", requestErr) - return false - } - latestCheckpoint = has.CurrentLedger - return latestCheckpoint >= ledgerSeq - } - assert.Eventually(t, f, waitTime, 1*time.Second) + assert.Eventually(t, + func() bool { + has, requestErr := archive.GetRootHAS() + if requestErr != nil { + t.Logf("Request to fetch checkpoint failed: %v", requestErr) + return false + } + latestCheckpoint = has.CurrentLedger + return latestCheckpoint >= ledgerSeq + + }, + waitTime, + 1*time.Second) } func getExactUpgradedLedgerSeq(ledgerMap map[uint32]xdr.LedgerCloseMeta, version uint32) uint32 { diff --git a/services/horizon/internal/test/integration/integration.go b/services/horizon/internal/test/integration/integration.go index 5d2b368afa..f90eece01f 100644 --- a/services/horizon/internal/test/integration/integration.go +++ b/services/horizon/internal/test/integration/integration.go @@ -614,9 +614,9 @@ func CreateCaptiveCoreConfig(contents string) (string, string, func()) { } } -func (i *Test) CreateCaptiveCoreConfig() (*ledgerbackend.CaptiveCoreConfig, func(), error) { - +func (i *Test) CreateCaptiveCoreConfig() (*ledgerbackend.CaptiveCoreConfig, error) { confName, storagePath, cleanupFn := CreateCaptiveCoreConfig(SimpleCaptiveCoreToml) + i.t.Cleanup(cleanupFn) i.t.Logf("Creating Captive Core config files, ConfName: %v, storagePath: %v", confName, storagePath) captiveCoreConfig := ledgerbackend.CaptiveCoreConfig{ @@ -636,11 +636,11 @@ func (i *Test) CreateCaptiveCoreConfig() (*ledgerbackend.CaptiveCoreConfig, func toml, err := ledgerbackend.NewCaptiveCoreTomlFromData([]byte(SimpleCaptiveCoreToml), tomlParams) if err != nil { - return nil, func() {}, err + return nil, err } captiveCoreConfig.Toml = toml - return &captiveCoreConfig, cleanupFn, nil + return &captiveCoreConfig, nil } const maxWaitForCoreStartup = 30 * time.Second