From a1f0f73a4090d63120b59ef9a87be6fe8f22bd5c Mon Sep 17 00:00:00 2001 From: Simon Chow Date: Wed, 8 Jan 2025 17:33:12 -0500 Subject: [PATCH 1/7] xdrill for ledgerCloseMeta --- exp/xdrill/ledger.go | 248 ++++++++++++++++++++++++++++++++++++++ exp/xdrill/ledger_test.go | 168 ++++++++++++++++++++++++++ exp/xdrill/utils/utils.go | 92 ++++++++++++++ go.mod | 1 + go.sum | 2 + 5 files changed, 511 insertions(+) create mode 100644 exp/xdrill/ledger.go create mode 100644 exp/xdrill/ledger_test.go create mode 100644 exp/xdrill/utils/utils.go diff --git a/exp/xdrill/ledger.go b/exp/xdrill/ledger.go new file mode 100644 index 0000000000..3095094d85 --- /dev/null +++ b/exp/xdrill/ledger.go @@ -0,0 +1,248 @@ +package xdrill + +import ( + "encoding/base64" + "fmt" + "time" + + "github.com/stellar/go/exp/xdrill/utils" + "github.com/stellar/go/keypair" + "github.com/stellar/go/txnbuild" + "github.com/stellar/go/xdr" +) + +type Ledger struct { + ledger *xdr.LedgerCloseMeta +} + +func (l Ledger) Sequence() uint32 { + return uint32(l.ledger.LedgerHeaderHistoryEntry().Header.LedgerSeq) +} + +func (l Ledger) ID() int64 { + return utils.NewID(int32(l.Sequence()), 0, 0).ToInt64() +} + +func (l Ledger) Hash() string { + return utils.HashToHexString(l.ledger.LedgerHeaderHistoryEntry().Hash) +} + +func (l Ledger) PreviousHash() string { + return utils.HashToHexString(l.ledger.PreviousLedgerHash()) +} + +func (l Ledger) CloseTime() int64 { + return l.ledger.LedgerCloseTime() +} + +func (l Ledger) ClosedAt() time.Time { + return time.Unix(l.CloseTime(), 0).UTC() +} + +func (l Ledger) TotalCoins() int64 { + return int64(l.ledger.LedgerHeaderHistoryEntry().Header.TotalCoins) +} + +func (l Ledger) FeePool() int64 { + return int64(l.ledger.LedgerHeaderHistoryEntry().Header.FeePool) +} + +func (l Ledger) BaseFee() uint32 { + return uint32(l.ledger.LedgerHeaderHistoryEntry().Header.BaseFee) +} + +func (l Ledger) BaseReserve() uint32 { + return uint32(l.ledger.LedgerHeaderHistoryEntry().Header.BaseReserve) +} + +func (l Ledger) MaxTxSetSize() uint32 { + return uint32(l.ledger.LedgerHeaderHistoryEntry().Header.MaxTxSetSize) +} + +func (l Ledger) LedgerVersion() uint32 { + return uint32(l.ledger.LedgerHeaderHistoryEntry().Header.LedgerVersion) +} + +func (l Ledger) SorobanFeeWrite1Kb() (int64, bool) { + lcmV1, ok := l.ledger.GetV1() + if !ok { + return 0, false + } + + extV1, ok := lcmV1.Ext.GetV1() + if !ok { + return 0, false + } + + return int64(extV1.SorobanFeeWrite1Kb), true +} + +func (l Ledger) TotalByteSizeOfBucketList() (uint64, bool) { + lcmV1, ok := l.ledger.GetV1() + if !ok { + return 0, false + } + + return uint64(lcmV1.TotalByteSizeOfBucketList), true +} + +func (l Ledger) NodeID() (string, bool) { + LedgerCloseValueSignature, ok := l.ledger.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() + if !ok { + return "", false + + } + nodeID, ok := utils.GetAddress(LedgerCloseValueSignature.NodeId) + if !ok { + return "", false + } + + return nodeID, true +} + +func (l Ledger) Signature() (string, bool) { + LedgerCloseValueSignature, ok := l.ledger.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() + if !ok { + return "", false + } + + return base64.StdEncoding.EncodeToString(LedgerCloseValueSignature.Signature), true +} + +// Add docstring to larger, more complicated functions +func (l Ledger) TransactionCounts() (successTxCount, failedTxCount int32, ok bool) { + var results []xdr.TransactionResultMeta + + transactions := getTransactionSet(l) + switch l.ledger.V { + case 0: + results = l.ledger.V0.TxProcessing + case 1: + results = l.ledger.V1.TxProcessing + } + txCount := len(transactions) + if txCount != len(results) { + return 0, 0, false + } + + for i := 0; i < txCount; i++ { + if results[i].Result.Successful() { + successTxCount++ + } else { + failedTxCount++ + } + } + + return successTxCount, failedTxCount, true +} + +// Add docstring to larger, more complicated functions +func (l Ledger) OperationCounts() (operationCount, txSetOperationCount int32, ok bool) { + var results []xdr.TransactionResultMeta + + transactions := getTransactionSet(l) + switch l.ledger.V { + case 0: + results = l.ledger.V0.TxProcessing + case 1: + results = l.ledger.V1.TxProcessing + } + + txCount := len(transactions) + if txCount != len(results) { + return 0, 0, false + } + + for i := 0; i < txCount; i++ { + operations := transactions[i].Operations() + numberOfOps := int32(len(operations)) + txSetOperationCount += numberOfOps + + // for successful transactions, the operation count is based on the operations results slice + if results[i].Result.Successful() { + operationResults, ok := results[i].Result.OperationResults() + if !ok { + return 0, 0, false + } + + operationCount += int32(len(operationResults)) + } + + } + + return operationCount, txSetOperationCount, true +} + +func getTransactionSet(l Ledger) (transactionProcessing []xdr.TransactionEnvelope) { + switch l.ledger.V { + case 0: + return l.ledger.V0.TxSet.Txs + case 1: + switch l.ledger.V1.TxSet.V { + case 0: + return getTransactionPhase(l.ledger.V1.TxSet.V1TxSet.Phases) + default: + panic(fmt.Sprintf("unsupported LedgerCloseMeta.V1.TxSet.V: %d", l.ledger.V1.TxSet.V)) + } + default: + panic(fmt.Sprintf("unsupported LedgerCloseMeta.V: %d", l.ledger.V)) + } +} + +func getTransactionPhase(transactionPhase []xdr.TransactionPhase) (transactionEnvelope []xdr.TransactionEnvelope) { + transactionSlice := []xdr.TransactionEnvelope{} + for _, phase := range transactionPhase { + switch phase.V { + case 0: + components := phase.MustV0Components() + for _, component := range components { + switch component.Type { + case 0: + transactionSlice = append(transactionSlice, component.TxsMaybeDiscountedFee.Txs...) + + default: + panic(fmt.Sprintf("unsupported TxSetComponentType: %d", component.Type)) + } + + } + default: + panic(fmt.Sprintf("unsupported TransactionPhase.V: %d", phase.V)) + } + } + + return transactionSlice +} + +func CreateSampleTx(sequence int64, operationCount int) xdr.TransactionEnvelope { + kp, err := keypair.Random() + PanicOnError(err) + + operations := []txnbuild.Operation{} + operationType := &txnbuild.BumpSequence{ + BumpTo: 0, + } + for i := 0; i < operationCount; i++ { + operations = append(operations, operationType) + } + + sourceAccount := txnbuild.NewSimpleAccount(kp.Address(), int64(0)) + tx, err := txnbuild.NewTransaction( + txnbuild.TransactionParams{ + SourceAccount: &sourceAccount, + Operations: operations, + BaseFee: txnbuild.MinBaseFee, + Preconditions: txnbuild.Preconditions{TimeBounds: txnbuild.NewInfiniteTimeout()}, + }, + ) + PanicOnError(err) + + env := tx.ToXDR() + return env +} + +// PanicOnError is a function that panics if the provided error is not nil +func PanicOnError(err error) { + if err != nil { + panic(err) + } +} diff --git a/exp/xdrill/ledger_test.go b/exp/xdrill/ledger_test.go new file mode 100644 index 0000000000..febdf65d45 --- /dev/null +++ b/exp/xdrill/ledger_test.go @@ -0,0 +1,168 @@ +package xdrill + +import ( + "testing" + "time" + + "github.com/stellar/go/xdr" + "github.com/stretchr/testify/assert" +) + +func TestLedger(t *testing.T) { + ledger := Ledger{ + ledger: ledgerTestInput(), + } + + assert.Equal(t, uint32(30578981), ledger.Sequence()) + assert.Equal(t, int64(131335723340005376), ledger.ID()) + assert.Equal(t, "26932dc4d84b5fabe9ae744cb43ce4c6daccf98c86a991b2a14945b1adac4d59", ledger.Hash()) + assert.Equal(t, "f63c15d0eaf48afbd751a4c4dfade54a3448053c47c5a71d622668ae0cc2a208", ledger.PreviousHash()) + assert.Equal(t, int64(1594584547), ledger.CloseTime()) + assert.Equal(t, time.Time(time.Date(2020, time.July, 12, 20, 9, 7, 0, time.UTC)), ledger.ClosedAt()) + assert.Equal(t, int64(1054439020873472865), ledger.TotalCoins()) + assert.Equal(t, int64(18153766209161), ledger.FeePool()) + assert.Equal(t, uint32(100), ledger.BaseFee()) + assert.Equal(t, uint32(5000000), ledger.BaseReserve()) + assert.Equal(t, uint32(1000), ledger.MaxTxSetSize()) + assert.Equal(t, uint32(13), ledger.LedgerVersion()) + + var ok bool + + var freeWrite int64 + freeWrite, ok = ledger.SorobanFeeWrite1Kb() + assert.Equal(t, true, ok) + assert.Equal(t, int64(12), freeWrite) + + var bucketSize uint64 + bucketSize, ok = ledger.TotalByteSizeOfBucketList() + assert.Equal(t, true, ok) + assert.Equal(t, uint64(56), bucketSize) + + var nodeID string + nodeID, ok = ledger.NodeID() + assert.Equal(t, true, ok) + assert.Equal(t, "GARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA76O", nodeID) + + var signature string + signature, ok = ledger.Signature() + assert.Equal(t, true, ok) + assert.Equal(t, "", signature) + + var success int32 + var failed int32 + success, failed, ok = ledger.TransactionCounts() + assert.Equal(t, true, ok) + assert.Equal(t, int32(1), success) + assert.Equal(t, int32(1), failed) + + success, failed, ok = ledger.OperationCounts() + assert.Equal(t, true, ok) + assert.Equal(t, int32(1), success) + assert.Equal(t, int32(13), failed) + +} + +func ledgerTestInput() (lcm *xdr.LedgerCloseMeta) { + lcm = &xdr.LedgerCloseMeta{ + V: 1, + V1: &xdr.LedgerCloseMetaV1{ + Ext: xdr.LedgerCloseMetaExt{ + V: 1, + V1: &xdr.LedgerCloseMetaExtV1{ + SorobanFeeWrite1Kb: xdr.Int64(12), + }, + }, + LedgerHeader: xdr.LedgerHeaderHistoryEntry{ + Hash: xdr.Hash{0x26, 0x93, 0x2d, 0xc4, 0xd8, 0x4b, 0x5f, 0xab, 0xe9, 0xae, 0x74, 0x4c, 0xb4, 0x3c, 0xe4, 0xc6, 0xda, 0xcc, 0xf9, 0x8c, 0x86, 0xa9, 0x91, 0xb2, 0xa1, 0x49, 0x45, 0xb1, 0xad, 0xac, 0x4d, 0x59}, + Header: xdr.LedgerHeader{ + LedgerSeq: 30578981, + TotalCoins: 1054439020873472865, + FeePool: 18153766209161, + BaseFee: 100, + BaseReserve: 5000000, + MaxTxSetSize: 1000, + LedgerVersion: 13, + PreviousLedgerHash: xdr.Hash{0xf6, 0x3c, 0x15, 0xd0, 0xea, 0xf4, 0x8a, 0xfb, 0xd7, 0x51, 0xa4, 0xc4, 0xdf, 0xad, 0xe5, 0x4a, 0x34, 0x48, 0x5, 0x3c, 0x47, 0xc5, 0xa7, 0x1d, 0x62, 0x26, 0x68, 0xae, 0xc, 0xc2, 0xa2, 0x8}, + ScpValue: xdr.StellarValue{ + Ext: xdr.StellarValueExt{ + V: 1, + LcValueSignature: &xdr.LedgerCloseValueSignature{ + NodeId: xdr.NodeId{ + Type: 0, + Ed25519: &xdr.Uint256{34}, + }, + }, + }, + CloseTime: 1594584547, + }, + }, + }, + TotalByteSizeOfBucketList: xdr.Uint64(56), + TxSet: xdr.GeneralizedTransactionSet{ + V: 0, + V1TxSet: &xdr.TransactionSetV1{ + Phases: []xdr.TransactionPhase{ + { + V: 0, + V0Components: &[]xdr.TxSetComponent{ + { + Type: 0, + TxsMaybeDiscountedFee: &xdr.TxSetComponentTxsMaybeDiscountedFee{ + Txs: []xdr.TransactionEnvelope{ + CreateSampleTx(0, 3), + CreateSampleTx(1, 10), + }, + }, + }, + }, + }, + }, + }, + }, + TxProcessing: []xdr.TransactionResultMeta{ + { + Result: xdr.TransactionResultPair{ + Result: xdr.TransactionResult{ + Result: xdr.TransactionResultResult{ + Code: xdr.TransactionResultCodeTxSuccess, + Results: &[]xdr.OperationResult{ + { + Code: xdr.OperationResultCodeOpInner, + Tr: &xdr.OperationResultTr{ + Type: xdr.OperationTypeCreateAccount, + CreateAccountResult: &xdr.CreateAccountResult{ + Code: 0, + }, + }, + }, + }, + }, + }, + }, + }, + { + Result: xdr.TransactionResultPair{ + Result: xdr.TransactionResult{ + Result: xdr.TransactionResultResult{ + Code: xdr.TransactionResultCodeTxFailed, + Results: &[]xdr.OperationResult{ + { + Code: xdr.OperationResultCodeOpInner, + Tr: &xdr.OperationResultTr{ + Type: xdr.OperationTypeCreateAccount, + CreateAccountResult: &xdr.CreateAccountResult{ + Code: 0, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + return lcm +} diff --git a/exp/xdrill/utils/utils.go b/exp/xdrill/utils/utils.go new file mode 100644 index 0000000000..0bc8d30c3e --- /dev/null +++ b/exp/xdrill/utils/utils.go @@ -0,0 +1,92 @@ +package utils + +import ( + "encoding/hex" + + "github.com/stellar/go/strkey" + "github.com/stellar/go/xdr" +) + +// HashToHexString is utility function that converts and xdr.Hash type to a hex string +func HashToHexString(inputHash xdr.Hash) string { + sliceHash := inputHash[:] + hexString := hex.EncodeToString(sliceHash) + return hexString +} + +type ID struct { + LedgerSequence int32 + TransactionOrder int32 + OperationOrder int32 +} + +const ( + // LedgerMask is the bitmask to mask out ledger sequences in a + // TotalOrderID + LedgerMask = (1 << 32) - 1 + // TransactionMask is the bitmask to mask out transaction indexes + TransactionMask = (1 << 20) - 1 + // OperationMask is the bitmask to mask out operation indexes + OperationMask = (1 << 12) - 1 + + // LedgerShift is the number of bits to shift an int64 to target the + // ledger component + LedgerShift = 32 + // TransactionShift is the number of bits to shift an int64 to + // target the transaction component + TransactionShift = 12 + // OperationShift is the number of bits to shift an int64 to target + // the operation component + OperationShift = 0 +) + +// New creates a new total order ID +func NewID(ledger int32, tx int32, op int32) *ID { + return &ID{ + LedgerSequence: ledger, + TransactionOrder: tx, + OperationOrder: op, + } +} + +// ToInt64 converts this struct back into an int64 +func (id ID) ToInt64() (result int64) { + + if id.LedgerSequence < 0 { + panic("invalid ledger sequence") + } + + if id.TransactionOrder > TransactionMask { + panic("transaction order overflow") + } + + if id.OperationOrder > OperationMask { + panic("operation order overflow") + } + + result = result | ((int64(id.LedgerSequence) & LedgerMask) << LedgerShift) + result = result | ((int64(id.TransactionOrder) & TransactionMask) << TransactionShift) + result = result | ((int64(id.OperationOrder) & OperationMask) << OperationShift) + return +} + +// TODO: This should be moved into the go monorepo xdr functions +// Or nodeID should just be an xdr.AccountId but the error message would be incorrect +func GetAddress(nodeID xdr.NodeId) (string, bool) { + switch nodeID.Type { + case xdr.PublicKeyTypePublicKeyTypeEd25519: + ed, ok := nodeID.GetEd25519() + if !ok { + return "", false + } + raw := make([]byte, 32) + copy(raw, ed[:]) + encodedAddress, err := strkey.Encode(strkey.VersionByteAccountID, raw) + if err != nil { + return "", false + } + return encodedAddress, true + default: + return "", false + } +} diff --git a/go.mod b/go.mod index 341c85543e..6d7e5fab5d 100644 --- a/go.mod +++ b/go.mod @@ -58,6 +58,7 @@ require ( require ( github.com/cenkalti/backoff/v4 v4.3.0 + github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da github.com/docker/docker v27.3.1+incompatible github.com/docker/go-connections v0.5.0 github.com/fsouza/fake-gcs-server v1.49.2 diff --git a/go.sum b/go.sum index a3c8ce08fa..b071601459 100644 --- a/go.sum +++ b/go.sum @@ -116,6 +116,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/djherbis/fscache v0.10.1 h1:hDv+RGyvD+UDKyRYuLoVNbuRTnf2SrA2K3VyR1br9lk= From f1f97a9ec38c132461a187763c03f24d8f30e0a8 Mon Sep 17 00:00:00 2001 From: Simon Chow Date: Wed, 8 Jan 2025 17:41:04 -0500 Subject: [PATCH 2/7] go mod tidy --- go.mod | 1 - go.sum | 2 -- 2 files changed, 3 deletions(-) diff --git a/go.mod b/go.mod index 6d7e5fab5d..341c85543e 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,6 @@ require ( require ( github.com/cenkalti/backoff/v4 v4.3.0 - github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da github.com/docker/docker v27.3.1+incompatible github.com/docker/go-connections v0.5.0 github.com/fsouza/fake-gcs-server v1.49.2 diff --git a/go.sum b/go.sum index b071601459..a3c8ce08fa 100644 --- a/go.sum +++ b/go.sum @@ -116,8 +116,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= -github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/djherbis/fscache v0.10.1 h1:hDv+RGyvD+UDKyRYuLoVNbuRTnf2SrA2K3VyR1br9lk= From 36cfcbc9afa0590c7d1a067c5214dfe70496d61c Mon Sep 17 00:00:00 2001 From: Simon Chow Date: Thu, 9 Jan 2025 00:55:01 -0500 Subject: [PATCH 3/7] Move util functions from ledger.go to utils.go --- exp/xdrill/ledger.go | 36 ------------------------------------ exp/xdrill/ledger_test.go | 5 +++-- exp/xdrill/utils/utils.go | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/exp/xdrill/ledger.go b/exp/xdrill/ledger.go index 3095094d85..e904009d52 100644 --- a/exp/xdrill/ledger.go +++ b/exp/xdrill/ledger.go @@ -6,8 +6,6 @@ import ( "time" "github.com/stellar/go/exp/xdrill/utils" - "github.com/stellar/go/keypair" - "github.com/stellar/go/txnbuild" "github.com/stellar/go/xdr" ) @@ -212,37 +210,3 @@ func getTransactionPhase(transactionPhase []xdr.TransactionPhase) (transactionEn return transactionSlice } - -func CreateSampleTx(sequence int64, operationCount int) xdr.TransactionEnvelope { - kp, err := keypair.Random() - PanicOnError(err) - - operations := []txnbuild.Operation{} - operationType := &txnbuild.BumpSequence{ - BumpTo: 0, - } - for i := 0; i < operationCount; i++ { - operations = append(operations, operationType) - } - - sourceAccount := txnbuild.NewSimpleAccount(kp.Address(), int64(0)) - tx, err := txnbuild.NewTransaction( - txnbuild.TransactionParams{ - SourceAccount: &sourceAccount, - Operations: operations, - BaseFee: txnbuild.MinBaseFee, - Preconditions: txnbuild.Preconditions{TimeBounds: txnbuild.NewInfiniteTimeout()}, - }, - ) - PanicOnError(err) - - env := tx.ToXDR() - return env -} - -// PanicOnError is a function that panics if the provided error is not nil -func PanicOnError(err error) { - if err != nil { - panic(err) - } -} diff --git a/exp/xdrill/ledger_test.go b/exp/xdrill/ledger_test.go index febdf65d45..7626895abe 100644 --- a/exp/xdrill/ledger_test.go +++ b/exp/xdrill/ledger_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/stellar/go/exp/xdrill/utils" "github.com/stellar/go/xdr" "github.com/stretchr/testify/assert" ) @@ -109,8 +110,8 @@ func ledgerTestInput() (lcm *xdr.LedgerCloseMeta) { Type: 0, TxsMaybeDiscountedFee: &xdr.TxSetComponentTxsMaybeDiscountedFee{ Txs: []xdr.TransactionEnvelope{ - CreateSampleTx(0, 3), - CreateSampleTx(1, 10), + utils.CreateSampleTx(0, 3), + utils.CreateSampleTx(1, 10), }, }, }, diff --git a/exp/xdrill/utils/utils.go b/exp/xdrill/utils/utils.go index 0bc8d30c3e..645967679e 100644 --- a/exp/xdrill/utils/utils.go +++ b/exp/xdrill/utils/utils.go @@ -3,7 +3,9 @@ package utils import ( "encoding/hex" + "github.com/stellar/go/keypair" "github.com/stellar/go/strkey" + "github.com/stellar/go/txnbuild" "github.com/stellar/go/xdr" ) @@ -90,3 +92,37 @@ func GetAddress(nodeID xdr.NodeId) (string, bool) { return "", false } } + +func CreateSampleTx(sequence int64, operationCount int) xdr.TransactionEnvelope { + kp, err := keypair.Random() + PanicOnError(err) + + operations := []txnbuild.Operation{} + operationType := &txnbuild.BumpSequence{ + BumpTo: 0, + } + for i := 0; i < operationCount; i++ { + operations = append(operations, operationType) + } + + sourceAccount := txnbuild.NewSimpleAccount(kp.Address(), int64(0)) + tx, err := txnbuild.NewTransaction( + txnbuild.TransactionParams{ + SourceAccount: &sourceAccount, + Operations: operations, + BaseFee: txnbuild.MinBaseFee, + Preconditions: txnbuild.Preconditions{TimeBounds: txnbuild.NewInfiniteTimeout()}, + }, + ) + PanicOnError(err) + + env := tx.ToXDR() + return env +} + +// PanicOnError is a function that panics if the provided error is not nil +func PanicOnError(err error) { + if err != nil { + panic(err) + } +} From d064296aa0ece071cd1b873d55e346fe97713f4e Mon Sep 17 00:00:00 2001 From: Simon Chow Date: Thu, 9 Jan 2025 01:10:10 -0500 Subject: [PATCH 4/7] change some return values to pointers --- exp/xdrill/ledger.go | 48 ++++++++++++++++++--------------------- exp/xdrill/ledger_test.go | 25 ++++++++------------ xdr/ledger_close_meta.go | 12 ++++++++++ 3 files changed, 43 insertions(+), 42 deletions(-) diff --git a/exp/xdrill/ledger.go b/exp/xdrill/ledger.go index e904009d52..88eebde1c6 100644 --- a/exp/xdrill/ledger.go +++ b/exp/xdrill/ledger.go @@ -61,50 +61,56 @@ func (l Ledger) LedgerVersion() uint32 { return uint32(l.ledger.LedgerHeaderHistoryEntry().Header.LedgerVersion) } -func (l Ledger) SorobanFeeWrite1Kb() (int64, bool) { +func (l Ledger) SorobanFeeWrite1Kb() *int64 { lcmV1, ok := l.ledger.GetV1() if !ok { - return 0, false + return nil } extV1, ok := lcmV1.Ext.GetV1() if !ok { - return 0, false + return nil } - return int64(extV1.SorobanFeeWrite1Kb), true + result := int64(extV1.SorobanFeeWrite1Kb) + + return &result } -func (l Ledger) TotalByteSizeOfBucketList() (uint64, bool) { +func (l Ledger) TotalByteSizeOfBucketList() *uint64 { lcmV1, ok := l.ledger.GetV1() if !ok { - return 0, false + return nil } - return uint64(lcmV1.TotalByteSizeOfBucketList), true + result := uint64(lcmV1.TotalByteSizeOfBucketList) + + return &result } -func (l Ledger) NodeID() (string, bool) { +func (l Ledger) NodeID() *string { LedgerCloseValueSignature, ok := l.ledger.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() if !ok { - return "", false + return nil } nodeID, ok := utils.GetAddress(LedgerCloseValueSignature.NodeId) if !ok { - return "", false + return nil } - return nodeID, true + return &nodeID } -func (l Ledger) Signature() (string, bool) { +func (l Ledger) Signature() *string { LedgerCloseValueSignature, ok := l.ledger.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() if !ok { - return "", false + return nil } - return base64.StdEncoding.EncodeToString(LedgerCloseValueSignature.Signature), true + result := base64.StdEncoding.EncodeToString(LedgerCloseValueSignature.Signature) + + return &result } // Add docstring to larger, more complicated functions @@ -112,12 +118,7 @@ func (l Ledger) TransactionCounts() (successTxCount, failedTxCount int32, ok boo var results []xdr.TransactionResultMeta transactions := getTransactionSet(l) - switch l.ledger.V { - case 0: - results = l.ledger.V0.TxProcessing - case 1: - results = l.ledger.V1.TxProcessing - } + results = l.ledger.TxProcessing() txCount := len(transactions) if txCount != len(results) { return 0, 0, false @@ -139,12 +140,7 @@ func (l Ledger) OperationCounts() (operationCount, txSetOperationCount int32, ok var results []xdr.TransactionResultMeta transactions := getTransactionSet(l) - switch l.ledger.V { - case 0: - results = l.ledger.V0.TxProcessing - case 1: - results = l.ledger.V1.TxProcessing - } + results = l.ledger.TxProcessing() txCount := len(transactions) if txCount != len(results) { diff --git a/exp/xdrill/ledger_test.go b/exp/xdrill/ledger_test.go index 7626895abe..b5ca0377a1 100644 --- a/exp/xdrill/ledger_test.go +++ b/exp/xdrill/ledger_test.go @@ -29,25 +29,17 @@ func TestLedger(t *testing.T) { var ok bool - var freeWrite int64 - freeWrite, ok = ledger.SorobanFeeWrite1Kb() - assert.Equal(t, true, ok) - assert.Equal(t, int64(12), freeWrite) + freeWrite := ledger.SorobanFeeWrite1Kb() + assert.Equal(t, int64(12), *freeWrite) - var bucketSize uint64 - bucketSize, ok = ledger.TotalByteSizeOfBucketList() - assert.Equal(t, true, ok) - assert.Equal(t, uint64(56), bucketSize) + bucketSize := ledger.TotalByteSizeOfBucketList() + assert.Equal(t, uint64(56), *bucketSize) - var nodeID string - nodeID, ok = ledger.NodeID() - assert.Equal(t, true, ok) - assert.Equal(t, "GARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA76O", nodeID) + nodeID := ledger.NodeID() + assert.Equal(t, "GARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA76O", *nodeID) - var signature string - signature, ok = ledger.Signature() - assert.Equal(t, true, ok) - assert.Equal(t, "", signature) + signature := ledger.Signature() + assert.Equal(t, "9g==", *signature) var success int32 var failed int32 @@ -92,6 +84,7 @@ func ledgerTestInput() (lcm *xdr.LedgerCloseMeta) { Type: 0, Ed25519: &xdr.Uint256{34}, }, + Signature: []byte{0xf6}, }, }, CloseTime: 1594584547, diff --git a/xdr/ledger_close_meta.go b/xdr/ledger_close_meta.go index 30e80b2e38..bcb4fd9645 100644 --- a/xdr/ledger_close_meta.go +++ b/xdr/ledger_close_meta.go @@ -156,3 +156,15 @@ func (l LedgerCloseMeta) EvictedPersistentLedgerEntries() ([]LedgerEntry, error) panic(fmt.Sprintf("Unsupported LedgerCloseMeta.V: %d", l.V)) } } + +// TxProcessing returns the TransactionResultMeta in this ledger +func (l LedgerCloseMeta) TxProcessing() []TransactionResultMeta { + switch l.V { + case 0: + return l.MustV0().TxProcessing + case 1: + return l.MustV1().TxProcessing + default: + panic(fmt.Sprintf("Unsupported LedgerCloseMeta.V: %d", l.V)) + } +} From 15fe1e574f47cd9a0d42dc7b86094200db12e9ca Mon Sep 17 00:00:00 2001 From: Simon Chow Date: Thu, 9 Jan 2025 17:01:08 -0500 Subject: [PATCH 5/7] Change ledger to Ledger --- exp/xdrill/ledger.go | 46 +++++++++++++++++++-------------------- exp/xdrill/ledger_test.go | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/exp/xdrill/ledger.go b/exp/xdrill/ledger.go index 88eebde1c6..d794fce04a 100644 --- a/exp/xdrill/ledger.go +++ b/exp/xdrill/ledger.go @@ -10,11 +10,11 @@ import ( ) type Ledger struct { - ledger *xdr.LedgerCloseMeta + Ledger *xdr.LedgerCloseMeta } func (l Ledger) Sequence() uint32 { - return uint32(l.ledger.LedgerHeaderHistoryEntry().Header.LedgerSeq) + return uint32(l.Ledger.LedgerHeaderHistoryEntry().Header.LedgerSeq) } func (l Ledger) ID() int64 { @@ -22,15 +22,15 @@ func (l Ledger) ID() int64 { } func (l Ledger) Hash() string { - return utils.HashToHexString(l.ledger.LedgerHeaderHistoryEntry().Hash) + return utils.HashToHexString(l.Ledger.LedgerHeaderHistoryEntry().Hash) } func (l Ledger) PreviousHash() string { - return utils.HashToHexString(l.ledger.PreviousLedgerHash()) + return utils.HashToHexString(l.Ledger.PreviousLedgerHash()) } func (l Ledger) CloseTime() int64 { - return l.ledger.LedgerCloseTime() + return l.Ledger.LedgerCloseTime() } func (l Ledger) ClosedAt() time.Time { @@ -38,31 +38,31 @@ func (l Ledger) ClosedAt() time.Time { } func (l Ledger) TotalCoins() int64 { - return int64(l.ledger.LedgerHeaderHistoryEntry().Header.TotalCoins) + return int64(l.Ledger.LedgerHeaderHistoryEntry().Header.TotalCoins) } func (l Ledger) FeePool() int64 { - return int64(l.ledger.LedgerHeaderHistoryEntry().Header.FeePool) + return int64(l.Ledger.LedgerHeaderHistoryEntry().Header.FeePool) } func (l Ledger) BaseFee() uint32 { - return uint32(l.ledger.LedgerHeaderHistoryEntry().Header.BaseFee) + return uint32(l.Ledger.LedgerHeaderHistoryEntry().Header.BaseFee) } func (l Ledger) BaseReserve() uint32 { - return uint32(l.ledger.LedgerHeaderHistoryEntry().Header.BaseReserve) + return uint32(l.Ledger.LedgerHeaderHistoryEntry().Header.BaseReserve) } func (l Ledger) MaxTxSetSize() uint32 { - return uint32(l.ledger.LedgerHeaderHistoryEntry().Header.MaxTxSetSize) + return uint32(l.Ledger.LedgerHeaderHistoryEntry().Header.MaxTxSetSize) } func (l Ledger) LedgerVersion() uint32 { - return uint32(l.ledger.LedgerHeaderHistoryEntry().Header.LedgerVersion) + return uint32(l.Ledger.LedgerHeaderHistoryEntry().Header.LedgerVersion) } func (l Ledger) SorobanFeeWrite1Kb() *int64 { - lcmV1, ok := l.ledger.GetV1() + lcmV1, ok := l.Ledger.GetV1() if !ok { return nil } @@ -78,7 +78,7 @@ func (l Ledger) SorobanFeeWrite1Kb() *int64 { } func (l Ledger) TotalByteSizeOfBucketList() *uint64 { - lcmV1, ok := l.ledger.GetV1() + lcmV1, ok := l.Ledger.GetV1() if !ok { return nil } @@ -89,7 +89,7 @@ func (l Ledger) TotalByteSizeOfBucketList() *uint64 { } func (l Ledger) NodeID() *string { - LedgerCloseValueSignature, ok := l.ledger.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() + LedgerCloseValueSignature, ok := l.Ledger.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() if !ok { return nil @@ -103,7 +103,7 @@ func (l Ledger) NodeID() *string { } func (l Ledger) Signature() *string { - LedgerCloseValueSignature, ok := l.ledger.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() + LedgerCloseValueSignature, ok := l.Ledger.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() if !ok { return nil } @@ -118,7 +118,7 @@ func (l Ledger) TransactionCounts() (successTxCount, failedTxCount int32, ok boo var results []xdr.TransactionResultMeta transactions := getTransactionSet(l) - results = l.ledger.TxProcessing() + results = l.Ledger.TxProcessing() txCount := len(transactions) if txCount != len(results) { return 0, 0, false @@ -140,7 +140,7 @@ func (l Ledger) OperationCounts() (operationCount, txSetOperationCount int32, ok var results []xdr.TransactionResultMeta transactions := getTransactionSet(l) - results = l.ledger.TxProcessing() + results = l.Ledger.TxProcessing() txCount := len(transactions) if txCount != len(results) { @@ -168,18 +168,18 @@ func (l Ledger) OperationCounts() (operationCount, txSetOperationCount int32, ok } func getTransactionSet(l Ledger) (transactionProcessing []xdr.TransactionEnvelope) { - switch l.ledger.V { + switch l.Ledger.V { case 0: - return l.ledger.V0.TxSet.Txs + return l.Ledger.V0.TxSet.Txs case 1: - switch l.ledger.V1.TxSet.V { + switch l.Ledger.V1.TxSet.V { case 0: - return getTransactionPhase(l.ledger.V1.TxSet.V1TxSet.Phases) + return getTransactionPhase(l.Ledger.V1.TxSet.V1TxSet.Phases) default: - panic(fmt.Sprintf("unsupported LedgerCloseMeta.V1.TxSet.V: %d", l.ledger.V1.TxSet.V)) + panic(fmt.Sprintf("unsupported LedgerCloseMeta.V1.TxSet.V: %d", l.Ledger.V1.TxSet.V)) } default: - panic(fmt.Sprintf("unsupported LedgerCloseMeta.V: %d", l.ledger.V)) + panic(fmt.Sprintf("unsupported LedgerCloseMeta.V: %d", l.Ledger.V)) } } diff --git a/exp/xdrill/ledger_test.go b/exp/xdrill/ledger_test.go index b5ca0377a1..9c135dd21d 100644 --- a/exp/xdrill/ledger_test.go +++ b/exp/xdrill/ledger_test.go @@ -11,7 +11,7 @@ import ( func TestLedger(t *testing.T) { ledger := Ledger{ - ledger: ledgerTestInput(), + Ledger: ledgerTestInput(), } assert.Equal(t, uint32(30578981), ledger.Sequence()) From d45b0fd0b35943076ea44efa733dd95c65d0a61c Mon Sep 17 00:00:00 2001 From: Simon Chow Date: Thu, 9 Jan 2025 19:58:42 -0500 Subject: [PATCH 6/7] Remove Ledger struct; pass lcm as param --- exp/xdrill/{ => ledger}/ledger.go | 92 ++++++++++++-------------- exp/xdrill/{ => ledger}/ledger_test.go | 49 +++++++------- 2 files changed, 67 insertions(+), 74 deletions(-) rename exp/xdrill/{ => ledger}/ledger.go (53%) rename exp/xdrill/{ => ledger}/ledger_test.go (78%) diff --git a/exp/xdrill/ledger.go b/exp/xdrill/ledger/ledger.go similarity index 53% rename from exp/xdrill/ledger.go rename to exp/xdrill/ledger/ledger.go index d794fce04a..b6cb9f9d48 100644 --- a/exp/xdrill/ledger.go +++ b/exp/xdrill/ledger/ledger.go @@ -1,4 +1,4 @@ -package xdrill +package ledger import ( "encoding/base64" @@ -9,60 +9,56 @@ import ( "github.com/stellar/go/xdr" ) -type Ledger struct { - Ledger *xdr.LedgerCloseMeta +func Sequence(l xdr.LedgerCloseMeta) uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.LedgerSeq) } -func (l Ledger) Sequence() uint32 { - return uint32(l.Ledger.LedgerHeaderHistoryEntry().Header.LedgerSeq) +func ID(l xdr.LedgerCloseMeta) int64 { + return utils.NewID(int32(l.LedgerSequence()), 0, 0).ToInt64() } -func (l Ledger) ID() int64 { - return utils.NewID(int32(l.Sequence()), 0, 0).ToInt64() +func Hash(l xdr.LedgerCloseMeta) string { + return utils.HashToHexString(l.LedgerHeaderHistoryEntry().Hash) } -func (l Ledger) Hash() string { - return utils.HashToHexString(l.Ledger.LedgerHeaderHistoryEntry().Hash) +func PreviousHash(l xdr.LedgerCloseMeta) string { + return utils.HashToHexString(l.PreviousLedgerHash()) } -func (l Ledger) PreviousHash() string { - return utils.HashToHexString(l.Ledger.PreviousLedgerHash()) +func CloseTime(l xdr.LedgerCloseMeta) int64 { + return l.LedgerCloseTime() } -func (l Ledger) CloseTime() int64 { - return l.Ledger.LedgerCloseTime() +func ClosedAt(l xdr.LedgerCloseMeta) time.Time { + return time.Unix(l.LedgerCloseTime(), 0).UTC() } -func (l Ledger) ClosedAt() time.Time { - return time.Unix(l.CloseTime(), 0).UTC() +func TotalCoins(l xdr.LedgerCloseMeta) int64 { + return int64(l.LedgerHeaderHistoryEntry().Header.TotalCoins) } -func (l Ledger) TotalCoins() int64 { - return int64(l.Ledger.LedgerHeaderHistoryEntry().Header.TotalCoins) +func FeePool(l xdr.LedgerCloseMeta) int64 { + return int64(l.LedgerHeaderHistoryEntry().Header.FeePool) } -func (l Ledger) FeePool() int64 { - return int64(l.Ledger.LedgerHeaderHistoryEntry().Header.FeePool) +func BaseFee(l xdr.LedgerCloseMeta) uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.BaseFee) } -func (l Ledger) BaseFee() uint32 { - return uint32(l.Ledger.LedgerHeaderHistoryEntry().Header.BaseFee) +func BaseReserve(l xdr.LedgerCloseMeta) uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.BaseReserve) } -func (l Ledger) BaseReserve() uint32 { - return uint32(l.Ledger.LedgerHeaderHistoryEntry().Header.BaseReserve) +func MaxTxSetSize(l xdr.LedgerCloseMeta) uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.MaxTxSetSize) } -func (l Ledger) MaxTxSetSize() uint32 { - return uint32(l.Ledger.LedgerHeaderHistoryEntry().Header.MaxTxSetSize) +func LedgerVersion(l xdr.LedgerCloseMeta) uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.LedgerVersion) } -func (l Ledger) LedgerVersion() uint32 { - return uint32(l.Ledger.LedgerHeaderHistoryEntry().Header.LedgerVersion) -} - -func (l Ledger) SorobanFeeWrite1Kb() *int64 { - lcmV1, ok := l.Ledger.GetV1() +func SorobanFeeWrite1Kb(l xdr.LedgerCloseMeta) *int64 { + lcmV1, ok := l.GetV1() if !ok { return nil } @@ -77,8 +73,8 @@ func (l Ledger) SorobanFeeWrite1Kb() *int64 { return &result } -func (l Ledger) TotalByteSizeOfBucketList() *uint64 { - lcmV1, ok := l.Ledger.GetV1() +func TotalByteSizeOfBucketList(l xdr.LedgerCloseMeta) *uint64 { + lcmV1, ok := l.GetV1() if !ok { return nil } @@ -88,8 +84,8 @@ func (l Ledger) TotalByteSizeOfBucketList() *uint64 { return &result } -func (l Ledger) NodeID() *string { - LedgerCloseValueSignature, ok := l.Ledger.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() +func NodeID(l xdr.LedgerCloseMeta) *string { + LedgerCloseValueSignature, ok := l.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() if !ok { return nil @@ -102,8 +98,8 @@ func (l Ledger) NodeID() *string { return &nodeID } -func (l Ledger) Signature() *string { - LedgerCloseValueSignature, ok := l.Ledger.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() +func Signature(l xdr.LedgerCloseMeta) *string { + LedgerCloseValueSignature, ok := l.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() if !ok { return nil } @@ -114,11 +110,11 @@ func (l Ledger) Signature() *string { } // Add docstring to larger, more complicated functions -func (l Ledger) TransactionCounts() (successTxCount, failedTxCount int32, ok bool) { +func TransactionCounts(l xdr.LedgerCloseMeta) (successTxCount, failedTxCount int32, ok bool) { var results []xdr.TransactionResultMeta transactions := getTransactionSet(l) - results = l.Ledger.TxProcessing() + results = l.TxProcessing() txCount := len(transactions) if txCount != len(results) { return 0, 0, false @@ -136,11 +132,11 @@ func (l Ledger) TransactionCounts() (successTxCount, failedTxCount int32, ok boo } // Add docstring to larger, more complicated functions -func (l Ledger) OperationCounts() (operationCount, txSetOperationCount int32, ok bool) { +func OperationCounts(l xdr.LedgerCloseMeta) (operationCount, txSetOperationCount int32, ok bool) { var results []xdr.TransactionResultMeta transactions := getTransactionSet(l) - results = l.Ledger.TxProcessing() + results = l.TxProcessing() txCount := len(transactions) if txCount != len(results) { @@ -167,19 +163,19 @@ func (l Ledger) OperationCounts() (operationCount, txSetOperationCount int32, ok return operationCount, txSetOperationCount, true } -func getTransactionSet(l Ledger) (transactionProcessing []xdr.TransactionEnvelope) { - switch l.Ledger.V { +func getTransactionSet(l xdr.LedgerCloseMeta) (transactionProcessing []xdr.TransactionEnvelope) { + switch l.V { case 0: - return l.Ledger.V0.TxSet.Txs + return l.V0.TxSet.Txs case 1: - switch l.Ledger.V1.TxSet.V { + switch l.V1.TxSet.V { case 0: - return getTransactionPhase(l.Ledger.V1.TxSet.V1TxSet.Phases) + return getTransactionPhase(l.V1.TxSet.V1TxSet.Phases) default: - panic(fmt.Sprintf("unsupported LedgerCloseMeta.V1.TxSet.V: %d", l.Ledger.V1.TxSet.V)) + panic(fmt.Sprintf("unsupported LedgerCloseMeta.V1.TxSet.V: %d", l.V1.TxSet.V)) } default: - panic(fmt.Sprintf("unsupported LedgerCloseMeta.V: %d", l.Ledger.V)) + panic(fmt.Sprintf("unsupported LedgerCloseMeta.V: %d", l.V)) } } diff --git a/exp/xdrill/ledger_test.go b/exp/xdrill/ledger/ledger_test.go similarity index 78% rename from exp/xdrill/ledger_test.go rename to exp/xdrill/ledger/ledger_test.go index 9c135dd21d..7adac0a4cb 100644 --- a/exp/xdrill/ledger_test.go +++ b/exp/xdrill/ledger/ledger_test.go @@ -1,4 +1,4 @@ -package xdrill +package ledger import ( "testing" @@ -10,53 +10,50 @@ import ( ) func TestLedger(t *testing.T) { - ledger := Ledger{ - Ledger: ledgerTestInput(), - } - - assert.Equal(t, uint32(30578981), ledger.Sequence()) - assert.Equal(t, int64(131335723340005376), ledger.ID()) - assert.Equal(t, "26932dc4d84b5fabe9ae744cb43ce4c6daccf98c86a991b2a14945b1adac4d59", ledger.Hash()) - assert.Equal(t, "f63c15d0eaf48afbd751a4c4dfade54a3448053c47c5a71d622668ae0cc2a208", ledger.PreviousHash()) - assert.Equal(t, int64(1594584547), ledger.CloseTime()) - assert.Equal(t, time.Time(time.Date(2020, time.July, 12, 20, 9, 7, 0, time.UTC)), ledger.ClosedAt()) - assert.Equal(t, int64(1054439020873472865), ledger.TotalCoins()) - assert.Equal(t, int64(18153766209161), ledger.FeePool()) - assert.Equal(t, uint32(100), ledger.BaseFee()) - assert.Equal(t, uint32(5000000), ledger.BaseReserve()) - assert.Equal(t, uint32(1000), ledger.MaxTxSetSize()) - assert.Equal(t, uint32(13), ledger.LedgerVersion()) + ledger := ledgerTestInput() - var ok bool + assert.Equal(t, uint32(30578981), Sequence(ledger)) + assert.Equal(t, int64(131335723340005376), ID(ledger)) + assert.Equal(t, "26932dc4d84b5fabe9ae744cb43ce4c6daccf98c86a991b2a14945b1adac4d59", Hash(ledger)) + assert.Equal(t, "f63c15d0eaf48afbd751a4c4dfade54a3448053c47c5a71d622668ae0cc2a208", PreviousHash(ledger)) + assert.Equal(t, int64(1594584547), CloseTime(ledger)) + assert.Equal(t, time.Time(time.Date(2020, time.July, 12, 20, 9, 7, 0, time.UTC)), ClosedAt(ledger)) + assert.Equal(t, int64(1054439020873472865), TotalCoins(ledger)) + assert.Equal(t, int64(18153766209161), FeePool(ledger)) + assert.Equal(t, uint32(100), BaseFee(ledger)) + assert.Equal(t, uint32(5000000), BaseReserve(ledger)) + assert.Equal(t, uint32(1000), MaxTxSetSize(ledger)) + assert.Equal(t, uint32(13), LedgerVersion(ledger)) - freeWrite := ledger.SorobanFeeWrite1Kb() + freeWrite := SorobanFeeWrite1Kb(ledger) assert.Equal(t, int64(12), *freeWrite) - bucketSize := ledger.TotalByteSizeOfBucketList() + bucketSize := TotalByteSizeOfBucketList(ledger) assert.Equal(t, uint64(56), *bucketSize) - nodeID := ledger.NodeID() + nodeID := NodeID(ledger) assert.Equal(t, "GARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA76O", *nodeID) - signature := ledger.Signature() + signature := Signature(ledger) assert.Equal(t, "9g==", *signature) + var ok bool var success int32 var failed int32 - success, failed, ok = ledger.TransactionCounts() + success, failed, ok = TransactionCounts(ledger) assert.Equal(t, true, ok) assert.Equal(t, int32(1), success) assert.Equal(t, int32(1), failed) - success, failed, ok = ledger.OperationCounts() + success, failed, ok = OperationCounts(ledger) assert.Equal(t, true, ok) assert.Equal(t, int32(1), success) assert.Equal(t, int32(13), failed) } -func ledgerTestInput() (lcm *xdr.LedgerCloseMeta) { - lcm = &xdr.LedgerCloseMeta{ +func ledgerTestInput() (lcm xdr.LedgerCloseMeta) { + lcm = xdr.LedgerCloseMeta{ V: 1, V1: &xdr.LedgerCloseMetaV1{ Ext: xdr.LedgerCloseMetaExt{ From e9de99c352853a1043800b3a9a66fbf45649ee7e Mon Sep 17 00:00:00 2001 From: Simon Chow Date: Thu, 9 Jan 2025 16:59:20 -0500 Subject: [PATCH 7/7] xdrill transaction helper functions --- exp/xdrill/transaction/transaction.go | 359 +++++++++++++++++++++ exp/xdrill/transaction/transaction_test.go | 242 ++++++++++++++ exp/xdrill/utils/utils.go | 51 +++ 3 files changed, 652 insertions(+) create mode 100644 exp/xdrill/transaction/transaction.go create mode 100644 exp/xdrill/transaction/transaction_test.go diff --git a/exp/xdrill/transaction/transaction.go b/exp/xdrill/transaction/transaction.go new file mode 100644 index 0000000000..15c1f1a664 --- /dev/null +++ b/exp/xdrill/transaction/transaction.go @@ -0,0 +1,359 @@ +package transaction + +import ( + "encoding/base64" + "encoding/hex" + "fmt" + "strconv" + + "github.com/stellar/go/exp/xdrill/ledger" + "github.com/stellar/go/exp/xdrill/utils" + "github.com/stellar/go/ingest" + "github.com/stellar/go/toid" + "github.com/stellar/go/xdr" +) + +func Hash(t ingest.LedgerTransaction) string { + return utils.HashToHexString(t.Result.TransactionHash) +} + +func Index(t ingest.LedgerTransaction) uint32 { + return uint32(t.Index) +} + +func ID(t ingest.LedgerTransaction, l xdr.LedgerCloseMeta) int64 { + return toid.New(int32(ledger.Sequence(l)), int32(t.Index), 0).ToInt64() +} + +func Account(t ingest.LedgerTransaction) (string, error) { + return utils.GetAccountAddressFromMuxedAccount(t.Envelope.SourceAccount()) +} + +func AccountSequence(t ingest.LedgerTransaction) int64 { + return t.Envelope.SeqNum() +} + +func MaxFee(t ingest.LedgerTransaction) uint32 { + return t.Envelope.Fee() +} + +func FeeCharged(t ingest.LedgerTransaction, l xdr.LedgerCloseMeta) *int64 { + // Any Soroban Fee Bump transactions before P21 will need the below logic to calculate the correct feeCharged + // Protocol 20 contained a bug where the feeCharged was incorrectly calculated but was fixed for + // Protocol 21 with https://github.com/stellar/stellar-core/issues/4188 + var result int64 + _, ok := getSorobanData(t) + if ok { + if ledger.LedgerVersion(l) < 21 && t.Envelope.Type == xdr.EnvelopeTypeEnvelopeTypeTxFeeBump { + resourceFeeRefund := SorobanResourceFeeRefund(t) + inclusionFeeCharged := SorobanInclusionFeeCharged(t) + result = int64(t.Result.Result.FeeCharged) - *resourceFeeRefund + *inclusionFeeCharged + return &result + } + } + + result = int64(t.Result.Result.FeeCharged) + + return &result +} + +func OperationCount(t ingest.LedgerTransaction) uint32 { + return uint32(len(t.Envelope.Operations())) +} + +func Memo(t ingest.LedgerTransaction) string { + memoObject := t.Envelope.Memo() + memoContents := "" + switch xdr.MemoType(memoObject.Type) { + case xdr.MemoTypeMemoText: + memoContents = memoObject.MustText() + case xdr.MemoTypeMemoId: + memoContents = strconv.FormatUint(uint64(memoObject.MustId()), 10) + case xdr.MemoTypeMemoHash: + hash := memoObject.MustHash() + memoContents = base64.StdEncoding.EncodeToString(hash[:]) + case xdr.MemoTypeMemoReturn: + hash := memoObject.MustRetHash() + memoContents = base64.StdEncoding.EncodeToString(hash[:]) + } + + return memoContents +} + +func MemoType(t ingest.LedgerTransaction) string { + memoObject := t.Envelope.Memo() + return memoObject.Type.String() +} + +func TimeBounds(t ingest.LedgerTransaction) (*string, error) { + timeBounds := t.Envelope.TimeBounds() + if timeBounds == nil { + return nil, nil + } + + if timeBounds.MaxTime < timeBounds.MinTime && timeBounds.MaxTime != 0 { + return nil, fmt.Errorf("the max time is earlier than the min time") + } + + var result string + if timeBounds.MaxTime == 0 { + result = fmt.Sprintf("[%d,)", timeBounds.MinTime) + return &result, nil + } + + result = fmt.Sprintf("[%d,%d)", timeBounds.MinTime, timeBounds.MaxTime) + + return &result, nil +} + +func LedgerBounds(t ingest.LedgerTransaction) *string { + ledgerBounds := t.Envelope.LedgerBounds() + if ledgerBounds == nil { + return nil + } + + result := fmt.Sprintf("[%d,%d)", int64(ledgerBounds.MinLedger), int64(ledgerBounds.MaxLedger)) + + return &result +} + +func MinSequence(t ingest.LedgerTransaction) *int64 { + return t.Envelope.MinSeqNum() +} + +func MinSequenceAge(t ingest.LedgerTransaction) *int64 { + minSequenceAge := t.Envelope.MinSeqAge() + if minSequenceAge == nil { + return nil + } + + minSequenceAgeInt64 := int64(*minSequenceAge) + return &minSequenceAgeInt64 +} + +func MinSequenceLedgerGap(t ingest.LedgerTransaction) *int64 { + minSequenceLedgerGap := t.Envelope.MinSeqLedgerGap() + result := int64(*minSequenceLedgerGap) + return &result +} + +func getSorobanData(t ingest.LedgerTransaction) (sorobanData xdr.SorobanTransactionData, ok bool) { + switch t.Envelope.Type { + case xdr.EnvelopeTypeEnvelopeTypeTx: + return t.Envelope.V1.Tx.Ext.GetSorobanData() + case xdr.EnvelopeTypeEnvelopeTypeTxFeeBump: + return t.Envelope.FeeBump.Tx.InnerTx.V1.Tx.Ext.GetSorobanData() + } + + return +} + +func SorobanResourceFee(t ingest.LedgerTransaction) *int64 { + sorobanData, ok := getSorobanData(t) + if !ok { + return nil + } + + result := int64(sorobanData.ResourceFee) + return &result +} + +func SorobanResourcesInstructions(t ingest.LedgerTransaction) *uint32 { + sorobanData, ok := getSorobanData(t) + if !ok { + return nil + } + + result := uint32(sorobanData.Resources.Instructions) + return &result +} + +func SorobanResourcesReadBytes(t ingest.LedgerTransaction) *uint32 { + sorobanData, ok := getSorobanData(t) + if !ok { + return nil + } + + result := uint32(sorobanData.Resources.ReadBytes) + return &result +} + +func SorobanResourcesWriteBytes(t ingest.LedgerTransaction) *uint32 { + sorobanData, ok := getSorobanData(t) + if !ok { + return nil + } + + result := uint32(sorobanData.Resources.WriteBytes) + return &result +} + +func InclusionFeeBid(t ingest.LedgerTransaction) *int64 { + resourceFee := SorobanResourceFee(t) + if resourceFee == nil { + return nil + } + + result := int64(t.Envelope.Fee()) - *resourceFee + return &result +} + +func getFeeAccountAddress(t ingest.LedgerTransaction) (feeAccountAddress string) { + switch t.Envelope.Type { + case xdr.EnvelopeTypeEnvelopeTypeTx: + sourceAccount := t.Envelope.SourceAccount() + feeAccountAddress = sourceAccount.Address() + case xdr.EnvelopeTypeEnvelopeTypeTxFeeBump: + feeBumpAccount := t.Envelope.FeeBumpAccount() + feeAccountAddress = feeBumpAccount.Address() + } + + return +} + +func SorobanInclusionFeeCharged(t ingest.LedgerTransaction) *int64 { + resourceFee := SorobanResourceFee(t) + if resourceFee == nil { + return nil + } + + accountBalanceStart, accountBalanceEnd := utils.GetAccountBalanceFromLedgerEntryChanges(t.FeeChanges, getFeeAccountAddress(t)) + initialFeeCharged := accountBalanceStart - accountBalanceEnd + result := initialFeeCharged - *resourceFee + + return &result +} + +func SorobanResourceFeeRefund(t ingest.LedgerTransaction) *int64 { + meta, ok := t.UnsafeMeta.GetV3() + if !ok { + return nil + } + + accountBalanceStart, accountBalanceEnd := utils.GetAccountBalanceFromLedgerEntryChanges(meta.TxChangesAfter, getFeeAccountAddress(t)) + result := accountBalanceEnd - accountBalanceStart + + return &result +} + +func SorobanTotalNonRefundableResourceFeeCharged(t ingest.LedgerTransaction) *int64 { + meta, ok := t.UnsafeMeta.GetV3() + if !ok { + return nil + } + + switch meta.SorobanMeta.Ext.V { + case 1: + result := int64(meta.SorobanMeta.Ext.V1.TotalNonRefundableResourceFeeCharged) + return &result + } + + return nil +} + +func SorobanTotalRefundableResourceFeeCharged(t ingest.LedgerTransaction) *int64 { + meta, ok := t.UnsafeMeta.GetV3() + if !ok { + return nil + } + + switch meta.SorobanMeta.Ext.V { + case 1: + result := int64(meta.SorobanMeta.Ext.V1.TotalRefundableResourceFeeCharged) + return &result + } + + return nil +} + +func SorobanRentFeeCharged(t ingest.LedgerTransaction) *int64 { + meta, ok := t.UnsafeMeta.GetV3() + if !ok { + return nil + } + + switch meta.SorobanMeta.Ext.V { + case 1: + result := int64(meta.SorobanMeta.Ext.V1.RentFeeCharged) + return &result + } + + return nil +} + +func ResultCode(t ingest.LedgerTransaction) string { + return t.Result.Result.Result.Code.String() +} + +func Signers(t ingest.LedgerTransaction) (signers []string) { + if t.Envelope.IsFeeBump() { + signers, _ = utils.GetTxSigners(t.Envelope.FeeBump.Signatures) + return + } + + signers, _ = utils.GetTxSigners(t.Envelope.Signatures()) + + return +} + +func AccountMuxed(t ingest.LedgerTransaction) *string { + sourceAccount := t.Envelope.SourceAccount() + if sourceAccount.Type != xdr.CryptoKeyTypeKeyTypeMuxedEd25519 { + return nil + } + + result := sourceAccount.Address() + + return &result +} + +func FeeAccount(t ingest.LedgerTransaction) *string { + if !t.Envelope.IsFeeBump() { + return nil + } + + feeBumpAccount := t.Envelope.FeeBumpAccount() + feeAccount := feeBumpAccount.ToAccountId() + result := feeAccount.Address() + + return &result +} + +func FeeAccountMuxed(t ingest.LedgerTransaction) *string { + if !t.Envelope.IsFeeBump() { + return nil + } + + feeBumpAccount := t.Envelope.FeeBumpAccount() + if feeBumpAccount.Type != xdr.CryptoKeyTypeKeyTypeMuxedEd25519 { + return nil + } + + result := feeBumpAccount.Address() + + return &result +} + +func InnerTransactionHash(t ingest.LedgerTransaction) *string { + if !t.Envelope.IsFeeBump() { + return nil + } + + innerHash := t.Result.InnerHash() + result := hex.EncodeToString(innerHash[:]) + + return &result +} + +func NewMaxFee(t ingest.LedgerTransaction) *uint32 { + if !t.Envelope.IsFeeBump() { + return nil + } + + newMaxFee := uint32(t.Envelope.FeeBumpFee()) + return &newMaxFee +} + +func Successful(t ingest.LedgerTransaction) bool { + return t.Result.Successful() +} diff --git a/exp/xdrill/transaction/transaction_test.go b/exp/xdrill/transaction/transaction_test.go new file mode 100644 index 0000000000..9dc36aa9ef --- /dev/null +++ b/exp/xdrill/transaction/transaction_test.go @@ -0,0 +1,242 @@ +package transaction + +import ( + "testing" + + "github.com/stellar/go/ingest" + "github.com/stellar/go/xdr" + "github.com/stretchr/testify/assert" +) + +func TestTransaction(t *testing.T) { + ledger := ledgerTestInput() + transaction := transactionTestInput() + + assert.Equal(t, "1122330000000000000000000000000000000000000000000000000000000000", Hash(transaction)) + assert.Equal(t, uint32(1), Index(transaction)) + assert.Equal(t, int64(131335723340009472), ID(transaction, ledger)) + + var err error + var account string + account, err = Account(transaction) + assert.Equal(t, nil, err) + assert.Equal(t, "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK", account) + + assert.Equal(t, int64(30578981), AccountSequence(transaction)) + assert.Equal(t, uint32(4560), MaxFee(transaction)) + + feeCharged := FeeCharged(transaction, ledger) + assert.Equal(t, int64(789), *feeCharged) + + assert.Equal(t, uint32(3), OperationCount(transaction)) + assert.Equal(t, "test memo", Memo(transaction)) + assert.Equal(t, "MemoTypeMemoText", MemoType(transaction)) + + var timeBounds *string + timeBounds, err = TimeBounds(transaction) + assert.Equal(t, nil, err) + assert.Equal(t, "[1,10)", *timeBounds) + + ledgerBounds := LedgerBounds(transaction) + assert.Equal(t, "[2,20)", *ledgerBounds) + + minSequence := MinSequence(transaction) + assert.Equal(t, int64(123), *minSequence) + + minSequenceAge := MinSequenceAge(transaction) + assert.Equal(t, int64(456), *minSequenceAge) + + minSequenceLedgerGap := MinSequenceLedgerGap(transaction) + assert.Equal(t, int64(789), *minSequenceLedgerGap) + + sorobanResourceFee := SorobanResourceFee(transaction) + assert.Equal(t, int64(1234), *sorobanResourceFee) + + sorobanResourcesInstructions := SorobanResourcesInstructions(transaction) + assert.Equal(t, uint32(123), *sorobanResourcesInstructions) + + sorobanResourcesReadBytes := SorobanResourcesReadBytes(transaction) + assert.Equal(t, uint32(456), *sorobanResourcesReadBytes) + + sorobanResourcesWriteBytes := SorobanResourcesWriteBytes(transaction) + assert.Equal(t, uint32(789), *sorobanResourcesWriteBytes) + + inclusionFeeBid := InclusionFeeBid(transaction) + assert.Equal(t, int64(3326), *inclusionFeeBid) + + sorobanInclusionFeeCharged := SorobanInclusionFeeCharged(transaction) + assert.Equal(t, int64(-1234), *sorobanInclusionFeeCharged) + + sorobanResourceFeeRefund := SorobanResourceFeeRefund(transaction) + assert.Equal(t, int64(0), *sorobanResourceFeeRefund) + + sorobanTotalNonRefundableResourceFeeCharged := SorobanTotalNonRefundableResourceFeeCharged(transaction) + assert.Equal(t, int64(321), *sorobanTotalNonRefundableResourceFeeCharged) + + sorobanTotalRefundableResourceFeeCharged := SorobanTotalRefundableResourceFeeCharged(transaction) + assert.Equal(t, int64(123), *sorobanTotalRefundableResourceFeeCharged) + + sorobanRentFeeCharged := SorobanRentFeeCharged(transaction) + assert.Equal(t, int64(456), *sorobanRentFeeCharged) + + assert.Equal(t, "TransactionResultCodeTxSuccess", ResultCode(transaction)) + assert.Equal(t, []string{"GAISFR7R"}, Signers(transaction)) + + accountMuxed := AccountMuxed(transaction) + assert.Equal(t, "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I", *accountMuxed) + + feeAccount := FeeAccount(transaction) + assert.Equal(t, (*string)(nil), feeAccount) + + feeAccountMuxed := FeeAccountMuxed(transaction) + assert.Equal(t, (*string)(nil), feeAccountMuxed) + + innerTransactionHash := InnerTransactionHash(transaction) + assert.Equal(t, (*string)(nil), innerTransactionHash) + + assert.Equal(t, (*uint32)(nil), NewMaxFee(transaction)) + assert.Equal(t, true, Successful(transaction)) +} + +func ledgerTestInput() (lcm xdr.LedgerCloseMeta) { + lcm = xdr.LedgerCloseMeta{ + V: 1, + V1: &xdr.LedgerCloseMetaV1{ + LedgerHeader: xdr.LedgerHeaderHistoryEntry{ + Header: xdr.LedgerHeader{ + LedgerSeq: 30578981, + LedgerVersion: 22, + }, + }, + }, + } + + return lcm +} + +func transactionTestInput() ingest.LedgerTransaction { + ed25519 := xdr.Uint256([32]byte{0x11, 0x22, 0x33}) + muxedAccount := xdr.MuxedAccount{ + Type: 256, + Ed25519: &ed25519, + Med25519: &xdr.MuxedAccountMed25519{ + Id: xdr.Uint64(123), + Ed25519: ed25519, + }, + } + + memoText := "test memo" + minSeqNum := xdr.SequenceNumber(123) + + transaction := ingest.LedgerTransaction{ + Index: 1, + Envelope: xdr.TransactionEnvelope{ + Type: xdr.EnvelopeTypeEnvelopeTypeTx, + V1: &xdr.TransactionV1Envelope{ + Signatures: []xdr.DecoratedSignature{ + { + Signature: []byte{0x11, 0x22}, + }, + }, + Tx: xdr.Transaction{ + SourceAccount: muxedAccount, + SeqNum: xdr.SequenceNumber(30578981), + Fee: xdr.Uint32(4560), + Operations: []xdr.Operation{ + { + SourceAccount: &muxedAccount, + Body: xdr.OperationBody{}, + }, + { + SourceAccount: &muxedAccount, + Body: xdr.OperationBody{}, + }, + { + SourceAccount: &muxedAccount, + Body: xdr.OperationBody{}, + }, + }, + Memo: xdr.Memo{ + Type: xdr.MemoTypeMemoText, + Text: &memoText, + }, + Cond: xdr.Preconditions{ + Type: 2, + V2: &xdr.PreconditionsV2{ + TimeBounds: &xdr.TimeBounds{ + MinTime: xdr.TimePoint(1), + MaxTime: xdr.TimePoint(10), + }, + LedgerBounds: &xdr.LedgerBounds{ + MinLedger: 2, + MaxLedger: 20, + }, + MinSeqNum: &minSeqNum, + MinSeqAge: 456, + MinSeqLedgerGap: 789, + }, + }, + Ext: xdr.TransactionExt{ + V: 1, + SorobanData: &xdr.SorobanTransactionData{ + Resources: xdr.SorobanResources{ + Instructions: 123, + ReadBytes: 456, + WriteBytes: 789, + }, + ResourceFee: 1234, + }, + }, + }, + }, + }, + Result: xdr.TransactionResultPair{ + TransactionHash: xdr.Hash{0x11, 0x22, 0x33}, + Result: xdr.TransactionResult{ + FeeCharged: xdr.Int64(789), + Result: xdr.TransactionResultResult{ + Code: 0, + }, + }, + }, + FeeChanges: xdr.LedgerEntryChanges{ + { + Type: xdr.LedgerEntryChangeTypeLedgerEntryState, + State: &xdr.LedgerEntry{ + Data: xdr.LedgerEntryData{ + Type: xdr.LedgerEntryTypeAccount, + Account: &xdr.AccountEntry{ + AccountId: xdr.AccountId{ + Type: 0, + Ed25519: &ed25519, + }, + Balance: 1000, + }, + }, + }, + }, + {}, + }, + UnsafeMeta: xdr.TransactionMeta{ + V: 3, + V3: &xdr.TransactionMetaV3{ + TxChangesAfter: xdr.LedgerEntryChanges{}, + SorobanMeta: &xdr.SorobanTransactionMeta{ + Ext: xdr.SorobanTransactionMetaExt{ + V: 1, + V1: &xdr.SorobanTransactionMetaExtV1{ + TotalNonRefundableResourceFeeCharged: 321, + TotalRefundableResourceFeeCharged: 123, + RentFeeCharged: 456, + }, + }, + }, + }, + }, + LedgerVersion: 22, + Ledger: xdr.LedgerCloseMeta{}, + Hash: xdr.Hash{}, + } + + return transaction +} diff --git a/exp/xdrill/utils/utils.go b/exp/xdrill/utils/utils.go index 645967679e..b5fc44329e 100644 --- a/exp/xdrill/utils/utils.go +++ b/exp/xdrill/utils/utils.go @@ -126,3 +126,54 @@ func PanicOnError(err error) { panic(err) } } + +// GetAccountAddressFromMuxedAccount takes in a muxed account and returns the address of the account +func GetAccountAddressFromMuxedAccount(account xdr.MuxedAccount) (string, error) { + providedID := account.ToAccountId() + pointerToID := &providedID + return pointerToID.GetAddress() +} + +func GetAccountBalanceFromLedgerEntryChanges(changes xdr.LedgerEntryChanges, sourceAccountAddress string) (int64, int64) { + var accountBalanceStart int64 + var accountBalanceEnd int64 + + for _, change := range changes { + switch change.Type { + case xdr.LedgerEntryChangeTypeLedgerEntryUpdated: + accountEntry, ok := change.Updated.Data.GetAccount() + if !ok { + continue + } + + if accountEntry.AccountId.Address() == sourceAccountAddress { + accountBalanceEnd = int64(accountEntry.Balance) + } + case xdr.LedgerEntryChangeTypeLedgerEntryState: + accountEntry, ok := change.State.Data.GetAccount() + if !ok { + continue + } + + if accountEntry.AccountId.Address() == sourceAccountAddress { + accountBalanceStart = int64(accountEntry.Balance) + } + } + } + + return accountBalanceStart, accountBalanceEnd +} + +func GetTxSigners(xdrSignatures []xdr.DecoratedSignature) ([]string, error) { + signers := make([]string, len(xdrSignatures)) + + for i, sig := range xdrSignatures { + signerAccount, err := strkey.Encode(strkey.VersionByteAccountID, sig.Signature) + if err != nil { + return nil, err + } + signers[i] = signerAccount + } + + return signers, nil +}