From d222d6e15967fba8cc99a7d31b9f94e2855d0ebd Mon Sep 17 00:00:00 2001 From: Simon Chow Date: Mon, 4 Nov 2024 10:41:14 -0500 Subject: [PATCH 1/4] XDRill ledger low level helpers example --- exp/xdrill/ledgerentries/ledger_entries.go | 1 + exp/xdrill/ledgers/ledgers.go | 191 +++++++++++++++++++++ exp/xdrill/operations/operations.go | 1 + exp/xdrill/transactions/transactions.go | 9 + exp/xdrill/transform_ledger.go | 104 +++++++++++ exp/xdrill/utils/utils.go | 92 ++++++++++ 6 files changed, 398 insertions(+) create mode 100644 exp/xdrill/ledgerentries/ledger_entries.go create mode 100644 exp/xdrill/ledgers/ledgers.go create mode 100644 exp/xdrill/operations/operations.go create mode 100644 exp/xdrill/transactions/transactions.go create mode 100644 exp/xdrill/transform_ledger.go create mode 100644 exp/xdrill/utils/utils.go diff --git a/exp/xdrill/ledgerentries/ledger_entries.go b/exp/xdrill/ledgerentries/ledger_entries.go new file mode 100644 index 0000000000..d6de63d65e --- /dev/null +++ b/exp/xdrill/ledgerentries/ledger_entries.go @@ -0,0 +1 @@ +package ledgerentries diff --git a/exp/xdrill/ledgers/ledgers.go b/exp/xdrill/ledgers/ledgers.go new file mode 100644 index 0000000000..4bb2005a38 --- /dev/null +++ b/exp/xdrill/ledgers/ledgers.go @@ -0,0 +1,191 @@ +package ledgers + +import ( + "encoding/base64" + "fmt" + "time" + + "github.com/stellar/go/exp/xdrill/utils" + "github.com/stellar/go/xdr" +) + +type Ledgers struct { + xdr.LedgerCloseMeta +} + +func (l Ledgers) Sequence() uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.LedgerSeq) +} + +func (l Ledgers) ID() int64 { + return utils.NewID(int32(l.LedgerSequence()), 0, 0).ToInt64() +} + +func (l Ledgers) Hash() string { + return utils.HashToHexString(l.LedgerHeaderHistoryEntry().Hash) +} + +func (l Ledgers) PreviousHash() string { + return utils.HashToHexString(l.PreviousLedgerHash()) +} + +func (l Ledgers) CloseTime() int64 { + return l.LedgerCloseTime() +} + +func (l Ledgers) ClosedAt() time.Time { + return time.Unix(l.CloseTime(), 0).UTC() +} + +func (l Ledgers) TotalCoins() int64 { + return int64(l.LedgerHeaderHistoryEntry().Header.TotalCoins) +} + +func (l Ledgers) FeePool() int64 { + return int64(l.LedgerHeaderHistoryEntry().Header.FeePool) +} + +func (l Ledgers) BaseFee() uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.BaseFee) +} + +func (l Ledgers) BaseReserve() uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.BaseReserve) +} + +func (l Ledgers) MaxTxSetSize() uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.MaxTxSetSize) +} + +func (l Ledgers) LedgerVersion() uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.LedgerVersion) +} + +func (l Ledgers) GetSorobanFeeWrite1Kb() (int64, bool) { + lcmV1, ok := l.GetV1() + if ok { + extV1, ok := lcmV1.Ext.GetV1() + if ok { + return int64(extV1.SorobanFeeWrite1Kb), true + } + } + + return 0, false +} + +func (l Ledgers) GetTotalByteSizeOfBucketList() (uint64, bool) { + lcmV1, ok := l.GetV1() + if ok { + return uint64(lcmV1.TotalByteSizeOfBucketList), true + } + + return 0, false +} + +func (l Ledgers) GetNodeID() (string, bool) { + LedgerCloseValueSignature, ok := l.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() + if ok { + nodeID, ok := utils.GetAddress(LedgerCloseValueSignature.NodeId) + if ok { + return nodeID, true + } + } + + return "", false +} + +func (l Ledgers) GetSignature() (string, bool) { + LedgerCloseValueSignature, ok := l.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() + if ok { + return base64.StdEncoding.EncodeToString(LedgerCloseValueSignature.Signature), true + } + + return "", false +} + +func (l Ledgers) GetTransactionCounts() (successTxCount, failedTxCount int32, ok bool) { + transactions := getTransactionSet(l) + results := l.V0.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 +} + +func (l Ledgers) GetOperationCounts() (operationCount, txSetOperationCount int32, ok bool) { + transactions := getTransactionSet(l) + results := l.V0.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 Ledgers) (transactionProcessing []xdr.TransactionEnvelope) { + switch l.V { + case 0: + return l.V0.TxSet.Txs + case 1: + switch l.V1.TxSet.V { + case 0: + return getTransactionPhase(l.V1.TxSet.V1TxSet.Phases) + default: + panic(fmt.Sprintf("unsupported LedgerCloseMeta.V1.TxSet.V: %d", l.V1.TxSet.V)) + } + default: + panic(fmt.Sprintf("unsupported LedgerCloseMeta.V: %d", l.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 +} diff --git a/exp/xdrill/operations/operations.go b/exp/xdrill/operations/operations.go new file mode 100644 index 0000000000..19c01a9d10 --- /dev/null +++ b/exp/xdrill/operations/operations.go @@ -0,0 +1 @@ +package operations diff --git a/exp/xdrill/transactions/transactions.go b/exp/xdrill/transactions/transactions.go new file mode 100644 index 0000000000..09f4012ea6 --- /dev/null +++ b/exp/xdrill/transactions/transactions.go @@ -0,0 +1,9 @@ +package transactions + +import ( + "github.com/stellar/go/ingest" +) + +type Transactions struct { + ingest.LedgerTransaction +} diff --git a/exp/xdrill/transform_ledger.go b/exp/xdrill/transform_ledger.go new file mode 100644 index 0000000000..fb857f9dc7 --- /dev/null +++ b/exp/xdrill/transform_ledger.go @@ -0,0 +1,104 @@ +package xdrill + +import ( + "fmt" + "time" + + "github.com/stellar/go/exp/xdrill/ledgers" + "github.com/stellar/go/xdr" +) + +type LedgerOutput struct { + Sequence uint32 `json:"sequence"` // sequence number of the ledger + LedgerHash string `json:"ledger_hash"` + PreviousLedgerHash string `json:"previous_ledger_hash"` + LedgerHeader string `json:"ledger_header"` // base 64 encoding of the ledger header + TransactionCount int32 `json:"transaction_count"` + OperationCount int32 `json:"operation_count"` // counts only operations that were a part of successful transactions + SuccessfulTransactionCount int32 `json:"successful_transaction_count"` + FailedTransactionCount int32 `json:"failed_transaction_count"` + TxSetOperationCount string `json:"tx_set_operation_count"` // counts all operations, even those that are part of failed transactions + ClosedAt time.Time `json:"closed_at"` // UTC timestamp + TotalCoins int64 `json:"total_coins"` + FeePool int64 `json:"fee_pool"` + BaseFee uint32 `json:"base_fee"` + BaseReserve uint32 `json:"base_reserve"` + MaxTxSetSize uint32 `json:"max_tx_set_size"` + ProtocolVersion uint32 `json:"protocol_version"` + LedgerID int64 `json:"id"` + SorobanFeeWrite1Kb int64 `json:"soroban_fee_write_1kb"` + NodeID string `json:"node_id"` + Signature string `json:"signature"` + TotalByteSizeOfBucketList uint64 `json:"total_byte_size_of_bucket_list"` +} + +func TransformLedger(lcm xdr.LedgerCloseMeta) (LedgerOutput, error) { + ledger := ledgers.Ledgers{ + LedgerCloseMeta: lcm, + } + + outputLedgerHeader, err := xdr.MarshalBase64(ledger.LedgerHeaderHistoryEntry().Header) + if err != nil { + return LedgerOutput{}, err + } + + outputSuccessfulTransactionCount, outputFailedTransactionCount, ok := ledger.GetTransactionCounts() + if !ok { + return LedgerOutput{}, fmt.Errorf("could not get transaction counts") + } + + outputOperationCount, outputTxSetOperationCount, ok := ledger.GetOperationCounts() + if !ok { + return LedgerOutput{}, fmt.Errorf("could not get operation counts") + } + + var outputSorobanFeeWrite1Kb int64 + sorobanFeeWrite1Kb, ok := ledger.GetSorobanFeeWrite1Kb() + if ok { + outputSorobanFeeWrite1Kb = sorobanFeeWrite1Kb + } + + var outputTotalByteSizeOfBucketList uint64 + totalByteSizeOfBucketList, ok := ledger.GetTotalByteSizeOfBucketList() + if ok { + outputTotalByteSizeOfBucketList = totalByteSizeOfBucketList + } + + var outputNodeID string + nodeID, ok := ledger.GetNodeID() + if ok { + outputNodeID = nodeID + } + + var outputSigature string + signature, ok := ledger.GetSignature() + if ok { + outputSigature = signature + } + + ledgerOutput := LedgerOutput{ + Sequence: ledger.LedgerSequence(), + LedgerHash: ledger.Hash(), + PreviousLedgerHash: ledger.Hash(), + LedgerHeader: outputLedgerHeader, + TransactionCount: outputSuccessfulTransactionCount, + OperationCount: outputOperationCount, + SuccessfulTransactionCount: outputSuccessfulTransactionCount, + FailedTransactionCount: outputFailedTransactionCount, + TxSetOperationCount: string(outputTxSetOperationCount), + ClosedAt: ledger.ClosedAt(), + TotalCoins: ledger.TotalCoins(), + FeePool: ledger.FeePool(), + BaseFee: ledger.BaseFee(), + BaseReserve: ledger.BaseReserve(), + MaxTxSetSize: ledger.MaxTxSetSize(), + ProtocolVersion: ledger.LedgerVersion(), + LedgerID: ledger.ID(), + SorobanFeeWrite1Kb: outputSorobanFeeWrite1Kb, + NodeID: outputNodeID, + Signature: outputSigature, + TotalByteSizeOfBucketList: outputTotalByteSizeOfBucketList, + } + + return ledgerOutput, nil +} 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 + } +} From 146dd525e7f8816819508cdcc2709fc6de3cdbf8 Mon Sep 17 00:00:00 2001 From: Simon Chow Date: Mon, 4 Nov 2024 12:53:04 -0500 Subject: [PATCH 2/4] Address comments --- exp/xdrill/ledgers/ledgers.go | 14 ++++++++------ exp/xdrill/transform_ledger.go | 24 ++++++++++++------------ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/exp/xdrill/ledgers/ledgers.go b/exp/xdrill/ledgers/ledgers.go index 4bb2005a38..130f52e19f 100644 --- a/exp/xdrill/ledgers/ledgers.go +++ b/exp/xdrill/ledgers/ledgers.go @@ -61,7 +61,7 @@ func (l Ledgers) LedgerVersion() uint32 { return uint32(l.LedgerHeaderHistoryEntry().Header.LedgerVersion) } -func (l Ledgers) GetSorobanFeeWrite1Kb() (int64, bool) { +func (l Ledgers) SorobanFeeWrite1Kb() (int64, bool) { lcmV1, ok := l.GetV1() if ok { extV1, ok := lcmV1.Ext.GetV1() @@ -73,7 +73,7 @@ func (l Ledgers) GetSorobanFeeWrite1Kb() (int64, bool) { return 0, false } -func (l Ledgers) GetTotalByteSizeOfBucketList() (uint64, bool) { +func (l Ledgers) TotalByteSizeOfBucketList() (uint64, bool) { lcmV1, ok := l.GetV1() if ok { return uint64(lcmV1.TotalByteSizeOfBucketList), true @@ -82,7 +82,7 @@ func (l Ledgers) GetTotalByteSizeOfBucketList() (uint64, bool) { return 0, false } -func (l Ledgers) GetNodeID() (string, bool) { +func (l Ledgers) NodeID() (string, bool) { LedgerCloseValueSignature, ok := l.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() if ok { nodeID, ok := utils.GetAddress(LedgerCloseValueSignature.NodeId) @@ -94,7 +94,7 @@ func (l Ledgers) GetNodeID() (string, bool) { return "", false } -func (l Ledgers) GetSignature() (string, bool) { +func (l Ledgers) Signature() (string, bool) { LedgerCloseValueSignature, ok := l.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() if ok { return base64.StdEncoding.EncodeToString(LedgerCloseValueSignature.Signature), true @@ -103,7 +103,8 @@ func (l Ledgers) GetSignature() (string, bool) { return "", false } -func (l Ledgers) GetTransactionCounts() (successTxCount, failedTxCount int32, ok bool) { +// Add docstring to larger, more complicated functions +func (l Ledgers) TransactionCounts() (successTxCount, failedTxCount int32, ok bool) { transactions := getTransactionSet(l) results := l.V0.TxProcessing txCount := len(transactions) @@ -122,7 +123,8 @@ func (l Ledgers) GetTransactionCounts() (successTxCount, failedTxCount int32, ok return successTxCount, failedTxCount, true } -func (l Ledgers) GetOperationCounts() (operationCount, txSetOperationCount int32, ok bool) { +// Add docstring to larger, more complicated functions +func (l Ledgers) OperationCounts() (operationCount, txSetOperationCount int32, ok bool) { transactions := getTransactionSet(l) results := l.V0.TxProcessing txCount := len(transactions) diff --git a/exp/xdrill/transform_ledger.go b/exp/xdrill/transform_ledger.go index fb857f9dc7..295e786b57 100644 --- a/exp/xdrill/transform_ledger.go +++ b/exp/xdrill/transform_ledger.go @@ -8,7 +8,7 @@ import ( "github.com/stellar/go/xdr" ) -type LedgerOutput struct { +type LedgerClosedOutput struct { Sequence uint32 `json:"sequence"` // sequence number of the ledger LedgerHash string `json:"ledger_hash"` PreviousLedgerHash string `json:"previous_ledger_hash"` @@ -32,51 +32,51 @@ type LedgerOutput struct { TotalByteSizeOfBucketList uint64 `json:"total_byte_size_of_bucket_list"` } -func TransformLedger(lcm xdr.LedgerCloseMeta) (LedgerOutput, error) { +func TransformLedger(lcm xdr.LedgerCloseMeta) (LedgerClosedOutput, error) { ledger := ledgers.Ledgers{ LedgerCloseMeta: lcm, } outputLedgerHeader, err := xdr.MarshalBase64(ledger.LedgerHeaderHistoryEntry().Header) if err != nil { - return LedgerOutput{}, err + return LedgerClosedOutput{}, err } - outputSuccessfulTransactionCount, outputFailedTransactionCount, ok := ledger.GetTransactionCounts() + outputSuccessfulTransactionCount, outputFailedTransactionCount, ok := ledger.TransactionCounts() if !ok { - return LedgerOutput{}, fmt.Errorf("could not get transaction counts") + return LedgerClosedOutput{}, fmt.Errorf("could not get transaction counts") } - outputOperationCount, outputTxSetOperationCount, ok := ledger.GetOperationCounts() + outputOperationCount, outputTxSetOperationCount, ok := ledger.OperationCounts() if !ok { - return LedgerOutput{}, fmt.Errorf("could not get operation counts") + return LedgerClosedOutput{}, fmt.Errorf("could not get operation counts") } var outputSorobanFeeWrite1Kb int64 - sorobanFeeWrite1Kb, ok := ledger.GetSorobanFeeWrite1Kb() + sorobanFeeWrite1Kb, ok := ledger.SorobanFeeWrite1Kb() if ok { outputSorobanFeeWrite1Kb = sorobanFeeWrite1Kb } var outputTotalByteSizeOfBucketList uint64 - totalByteSizeOfBucketList, ok := ledger.GetTotalByteSizeOfBucketList() + totalByteSizeOfBucketList, ok := ledger.TotalByteSizeOfBucketList() if ok { outputTotalByteSizeOfBucketList = totalByteSizeOfBucketList } var outputNodeID string - nodeID, ok := ledger.GetNodeID() + nodeID, ok := ledger.NodeID() if ok { outputNodeID = nodeID } var outputSigature string - signature, ok := ledger.GetSignature() + signature, ok := ledger.Signature() if ok { outputSigature = signature } - ledgerOutput := LedgerOutput{ + ledgerOutput := LedgerClosedOutput{ Sequence: ledger.LedgerSequence(), LedgerHash: ledger.Hash(), PreviousLedgerHash: ledger.Hash(), From 3b90190992bc678d96888168c8c11bdaaa457f9c Mon Sep 17 00:00:00 2001 From: Simon Chow Date: Mon, 4 Nov 2024 13:57:15 -0500 Subject: [PATCH 3/4] Make names singular instead of plural --- .../{ledgers/ledgers.go => ledger/ledger.go} | 42 +++++++++---------- exp/xdrill/ledgerentries/ledger_entries.go | 1 - exp/xdrill/ledgerentry/ledger_entry.go | 3 ++ exp/xdrill/operation/operation.go | 1 + exp/xdrill/operations/operations.go | 1 - exp/xdrill/transaction/transaction.go | 12 ++++++ exp/xdrill/transactions/transactions.go | 9 ---- exp/xdrill/transform_ledger.go | 6 ++- 8 files changed, 41 insertions(+), 34 deletions(-) rename exp/xdrill/{ledgers/ledgers.go => ledger/ledger.go} (79%) delete mode 100644 exp/xdrill/ledgerentries/ledger_entries.go create mode 100644 exp/xdrill/ledgerentry/ledger_entry.go create mode 100644 exp/xdrill/operation/operation.go delete mode 100644 exp/xdrill/operations/operations.go create mode 100644 exp/xdrill/transaction/transaction.go delete mode 100644 exp/xdrill/transactions/transactions.go diff --git a/exp/xdrill/ledgers/ledgers.go b/exp/xdrill/ledger/ledger.go similarity index 79% rename from exp/xdrill/ledgers/ledgers.go rename to exp/xdrill/ledger/ledger.go index 130f52e19f..fc749eee42 100644 --- a/exp/xdrill/ledgers/ledgers.go +++ b/exp/xdrill/ledger/ledger.go @@ -1,4 +1,4 @@ -package ledgers +package ledger import ( "encoding/base64" @@ -9,59 +9,59 @@ import ( "github.com/stellar/go/xdr" ) -type Ledgers struct { +type Ledger struct { xdr.LedgerCloseMeta } -func (l Ledgers) Sequence() uint32 { +func (l Ledger) Sequence() uint32 { return uint32(l.LedgerHeaderHistoryEntry().Header.LedgerSeq) } -func (l Ledgers) ID() int64 { +func (l Ledger) ID() int64 { return utils.NewID(int32(l.LedgerSequence()), 0, 0).ToInt64() } -func (l Ledgers) Hash() string { +func (l Ledger) Hash() string { return utils.HashToHexString(l.LedgerHeaderHistoryEntry().Hash) } -func (l Ledgers) PreviousHash() string { +func (l Ledger) PreviousHash() string { return utils.HashToHexString(l.PreviousLedgerHash()) } -func (l Ledgers) CloseTime() int64 { +func (l Ledger) CloseTime() int64 { return l.LedgerCloseTime() } -func (l Ledgers) ClosedAt() time.Time { +func (l Ledger) ClosedAt() time.Time { return time.Unix(l.CloseTime(), 0).UTC() } -func (l Ledgers) TotalCoins() int64 { +func (l Ledger) TotalCoins() int64 { return int64(l.LedgerHeaderHistoryEntry().Header.TotalCoins) } -func (l Ledgers) FeePool() int64 { +func (l Ledger) FeePool() int64 { return int64(l.LedgerHeaderHistoryEntry().Header.FeePool) } -func (l Ledgers) BaseFee() uint32 { +func (l Ledger) BaseFee() uint32 { return uint32(l.LedgerHeaderHistoryEntry().Header.BaseFee) } -func (l Ledgers) BaseReserve() uint32 { +func (l Ledger) BaseReserve() uint32 { return uint32(l.LedgerHeaderHistoryEntry().Header.BaseReserve) } -func (l Ledgers) MaxTxSetSize() uint32 { +func (l Ledger) MaxTxSetSize() uint32 { return uint32(l.LedgerHeaderHistoryEntry().Header.MaxTxSetSize) } -func (l Ledgers) LedgerVersion() uint32 { +func (l Ledger) LedgerVersion() uint32 { return uint32(l.LedgerHeaderHistoryEntry().Header.LedgerVersion) } -func (l Ledgers) SorobanFeeWrite1Kb() (int64, bool) { +func (l Ledger) SorobanFeeWrite1Kb() (int64, bool) { lcmV1, ok := l.GetV1() if ok { extV1, ok := lcmV1.Ext.GetV1() @@ -73,7 +73,7 @@ func (l Ledgers) SorobanFeeWrite1Kb() (int64, bool) { return 0, false } -func (l Ledgers) TotalByteSizeOfBucketList() (uint64, bool) { +func (l Ledger) TotalByteSizeOfBucketList() (uint64, bool) { lcmV1, ok := l.GetV1() if ok { return uint64(lcmV1.TotalByteSizeOfBucketList), true @@ -82,7 +82,7 @@ func (l Ledgers) TotalByteSizeOfBucketList() (uint64, bool) { return 0, false } -func (l Ledgers) NodeID() (string, bool) { +func (l Ledger) NodeID() (string, bool) { LedgerCloseValueSignature, ok := l.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() if ok { nodeID, ok := utils.GetAddress(LedgerCloseValueSignature.NodeId) @@ -94,7 +94,7 @@ func (l Ledgers) NodeID() (string, bool) { return "", false } -func (l Ledgers) Signature() (string, bool) { +func (l Ledger) Signature() (string, bool) { LedgerCloseValueSignature, ok := l.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() if ok { return base64.StdEncoding.EncodeToString(LedgerCloseValueSignature.Signature), true @@ -104,7 +104,7 @@ func (l Ledgers) Signature() (string, bool) { } // Add docstring to larger, more complicated functions -func (l Ledgers) TransactionCounts() (successTxCount, failedTxCount int32, ok bool) { +func (l Ledger) TransactionCounts() (successTxCount, failedTxCount int32, ok bool) { transactions := getTransactionSet(l) results := l.V0.TxProcessing txCount := len(transactions) @@ -124,7 +124,7 @@ func (l Ledgers) TransactionCounts() (successTxCount, failedTxCount int32, ok bo } // Add docstring to larger, more complicated functions -func (l Ledgers) OperationCounts() (operationCount, txSetOperationCount int32, ok bool) { +func (l Ledger) OperationCounts() (operationCount, txSetOperationCount int32, ok bool) { transactions := getTransactionSet(l) results := l.V0.TxProcessing txCount := len(transactions) @@ -152,7 +152,7 @@ func (l Ledgers) OperationCounts() (operationCount, txSetOperationCount int32, o return operationCount, txSetOperationCount, true } -func getTransactionSet(l Ledgers) (transactionProcessing []xdr.TransactionEnvelope) { +func getTransactionSet(l Ledger) (transactionProcessing []xdr.TransactionEnvelope) { switch l.V { case 0: return l.V0.TxSet.Txs diff --git a/exp/xdrill/ledgerentries/ledger_entries.go b/exp/xdrill/ledgerentries/ledger_entries.go deleted file mode 100644 index d6de63d65e..0000000000 --- a/exp/xdrill/ledgerentries/ledger_entries.go +++ /dev/null @@ -1 +0,0 @@ -package ledgerentries diff --git a/exp/xdrill/ledgerentry/ledger_entry.go b/exp/xdrill/ledgerentry/ledger_entry.go new file mode 100644 index 0000000000..fac64cf944 --- /dev/null +++ b/exp/xdrill/ledgerentry/ledger_entry.go @@ -0,0 +1,3 @@ +package ledgerentry + +// TODO: create low level helper functions diff --git a/exp/xdrill/operation/operation.go b/exp/xdrill/operation/operation.go new file mode 100644 index 0000000000..d820bc30bc --- /dev/null +++ b/exp/xdrill/operation/operation.go @@ -0,0 +1 @@ +package operation diff --git a/exp/xdrill/operations/operations.go b/exp/xdrill/operations/operations.go deleted file mode 100644 index 19c01a9d10..0000000000 --- a/exp/xdrill/operations/operations.go +++ /dev/null @@ -1 +0,0 @@ -package operations diff --git a/exp/xdrill/transaction/transaction.go b/exp/xdrill/transaction/transaction.go new file mode 100644 index 0000000000..afd7b60215 --- /dev/null +++ b/exp/xdrill/transaction/transaction.go @@ -0,0 +1,12 @@ +package transaction + +import ( + "github.com/stellar/go/ingest" +) + +type Transaction struct { + // Use ingest.LedgerTransaction to be used with TransactionReader + ingest.LedgerTransaction +} + +// TODO: create low level helper functions diff --git a/exp/xdrill/transactions/transactions.go b/exp/xdrill/transactions/transactions.go deleted file mode 100644 index 09f4012ea6..0000000000 --- a/exp/xdrill/transactions/transactions.go +++ /dev/null @@ -1,9 +0,0 @@ -package transactions - -import ( - "github.com/stellar/go/ingest" -) - -type Transactions struct { - ingest.LedgerTransaction -} diff --git a/exp/xdrill/transform_ledger.go b/exp/xdrill/transform_ledger.go index 295e786b57..fc78b911e6 100644 --- a/exp/xdrill/transform_ledger.go +++ b/exp/xdrill/transform_ledger.go @@ -1,10 +1,12 @@ +// Note: This is placed in the xdrill directory/package just for this example +// Processors may be placed in a different location/package; To be discussed package xdrill import ( "fmt" "time" - "github.com/stellar/go/exp/xdrill/ledgers" + "github.com/stellar/go/exp/xdrill/ledger" "github.com/stellar/go/xdr" ) @@ -33,7 +35,7 @@ type LedgerClosedOutput struct { } func TransformLedger(lcm xdr.LedgerCloseMeta) (LedgerClosedOutput, error) { - ledger := ledgers.Ledgers{ + ledger := ledger.Ledger{ LedgerCloseMeta: lcm, } From 6352001d34aa61152e2e7ea50975e4593b7fe8df Mon Sep 17 00:00:00 2001 From: Simon Chow Date: Fri, 8 Nov 2024 01:15:58 -0500 Subject: [PATCH 4/4] try helper functions in ingest and xdr --- exp/xdrill/operation/operation.go | 2 + exp/xdrill/transform_ledger_xdr.go | 64 +++++++ go.mod | 1 + go.sum | 2 + ingest/change.go | 12 ++ ingest/ledger_operation.go | 291 +++++++++++++++++++++++++++++ ingest/ledger_transaction.go | 32 +++- xdr/account_entry.go | 47 +++++ xdr/hash.go | 4 + xdr/ledger_close_meta.go | 154 +++++++++++++++ xdr/operation.go | 9 + xdr/operation_result_trace.go | 76 ++++++++ xdr/utils.go | 97 ++++++++++ 13 files changed, 788 insertions(+), 3 deletions(-) create mode 100644 exp/xdrill/transform_ledger_xdr.go create mode 100644 ingest/ledger_operation.go create mode 100644 xdr/operation.go create mode 100644 xdr/operation_result_trace.go create mode 100644 xdr/utils.go diff --git a/exp/xdrill/operation/operation.go b/exp/xdrill/operation/operation.go index d820bc30bc..b6afd5e7e2 100644 --- a/exp/xdrill/operation/operation.go +++ b/exp/xdrill/operation/operation.go @@ -1 +1,3 @@ package operation + +// TODO: create low level helper functions diff --git a/exp/xdrill/transform_ledger_xdr.go b/exp/xdrill/transform_ledger_xdr.go new file mode 100644 index 0000000000..dcfaa82808 --- /dev/null +++ b/exp/xdrill/transform_ledger_xdr.go @@ -0,0 +1,64 @@ +// Note: This is placed in the xdrill directory/package just for this example +// Processors may be placed in a different location/package; To be discussed +package xdrill + +import ( + "github.com/stellar/go/xdr" +) + +func TransformLedgerXDR(lcm xdr.LedgerCloseMeta) (LedgerClosedOutput, error) { + outputLedgerHeader, err := xdr.MarshalBase64(lcm.LedgerHeaderHistoryEntry().Header) + if err != nil { + return LedgerClosedOutput{}, err + } + + var outputSorobanFeeWrite1Kb int64 + sorobanFeeWrite1Kb, ok := lcm.SorobanFeeWrite1Kb() + if ok { + outputSorobanFeeWrite1Kb = sorobanFeeWrite1Kb + } + + var outputTotalByteSizeOfBucketList uint64 + totalByteSizeOfBucketList, ok := lcm.TotalByteSizeOfBucketList() + if ok { + outputTotalByteSizeOfBucketList = totalByteSizeOfBucketList + } + + var outputNodeID string + nodeID, ok := lcm.NodeID() + if ok { + outputNodeID = nodeID + } + + var outputSigature string + signature, ok := lcm.Signature() + if ok { + outputSigature = signature + } + + ledgerOutput := LedgerClosedOutput{ + Sequence: lcm.LedgerSequence(), + LedgerHash: lcm.LedgerHash().String(), + PreviousLedgerHash: lcm.PreviousLedgerHash().String(), + LedgerHeader: outputLedgerHeader, + TransactionCount: int32(lcm.CountTransactions()), + OperationCount: int32(lcm.CountOperations()), + SuccessfulTransactionCount: int32(lcm.CountSuccessfulTransactions()), + FailedTransactionCount: int32(lcm.CountFailedTransactions()), + TxSetOperationCount: string(lcm.CountSuccessfulOperations()), + ClosedAt: lcm.LedgerClosedAt(), + TotalCoins: lcm.TotalCoins(), + FeePool: lcm.FeePool(), + BaseFee: lcm.BaseFee(), + BaseReserve: lcm.BaseReserve(), + MaxTxSetSize: lcm.MaxTxSetSize(), + ProtocolVersion: lcm.ProtocolVersion(), + LedgerID: lcm.LedgerID(), + SorobanFeeWrite1Kb: outputSorobanFeeWrite1Kb, + NodeID: outputNodeID, + Signature: outputSigature, + TotalByteSizeOfBucketList: outputTotalByteSizeOfBucketList, + } + + return ledgerOutput, nil +} diff --git a/go.mod b/go.mod index 531a7ecd3f..e0f73a567e 100644 --- a/go.mod +++ b/go.mod @@ -60,6 +60,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.0.3+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 13d3a0acf0..830db50d41 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= diff --git a/ingest/change.go b/ingest/change.go index 0a2c063f1c..9b39f2f996 100644 --- a/ingest/change.go +++ b/ingest/change.go @@ -252,3 +252,15 @@ func (c Change) AccountChangedExceptSigners() (bool, error) { return !bytes.Equal(preBinary, postBinary), nil } + +// ExtractEntryFromChange gets the most recent state of an entry from an ingestion change, as well as if the entry was deleted +func (c Change) ExtractEntryFromChange() (xdr.LedgerEntry, xdr.LedgerEntryChangeType, bool, error) { + switch changeType := c.LedgerEntryChangeType(); changeType { + case xdr.LedgerEntryChangeTypeLedgerEntryCreated, xdr.LedgerEntryChangeTypeLedgerEntryUpdated: + return *c.Post, changeType, false, nil + case xdr.LedgerEntryChangeTypeLedgerEntryRemoved: + return *c.Pre, changeType, true, nil + default: + return xdr.LedgerEntry{}, changeType, false, fmt.Errorf("unable to extract ledger entry type from change") + } +} diff --git a/ingest/ledger_operation.go b/ingest/ledger_operation.go new file mode 100644 index 0000000000..e1e52e84a8 --- /dev/null +++ b/ingest/ledger_operation.go @@ -0,0 +1,291 @@ +package ingest + +import ( + "fmt" + "time" + + "github.com/dgryski/go-farm" + "github.com/guregu/null" + "github.com/stellar/go/amount" + "github.com/stellar/go/toid" + "github.com/stellar/go/xdr" +) + +type LedgerOperation struct { + OperationIndex int32 + Operation xdr.Operation + Transaction LedgerTransaction + LedgerCloseMeta xdr.LedgerCloseMeta +} + +func (o LedgerOperation) sourceAccountXDR() xdr.MuxedAccount { + sourceAccount := o.Operation.SourceAccount + if sourceAccount != nil { + return *sourceAccount + } + + return o.Transaction.Envelope.SourceAccount() +} + +func (o LedgerOperation) SourceAccount() string { + muxedAccount := o.sourceAccountXDR() + + providedID := muxedAccount.ToAccountId() + pointerToID := &providedID + return pointerToID.Address() +} + +func (o LedgerOperation) Type() int32 { + return int32(o.Operation.Body.Type) +} + +func (o LedgerOperation) TypeString() string { + return xdr.OperationTypeToStringMap[o.Type()] +} + +func (o LedgerOperation) ID() int64 { + //operationIndex needs +1 increment to stay in sync with ingest package + return toid.New(int32(o.LedgerCloseMeta.LedgerSequence()), int32(o.Transaction.Index), o.OperationIndex+1).ToInt64() +} + +func (o LedgerOperation) SourceAccountMuxed() null.String { + var address null.String + muxedAccount := o.sourceAccountXDR() + + if muxedAccount.Type == xdr.CryptoKeyTypeKeyTypeMuxedEd25519 { + return null.StringFrom(muxedAccount.Address()) + } + + return address +} + +func (o LedgerOperation) TransactionID() int64 { + return o.Transaction.TransactionID() +} + +func (o LedgerOperation) LedgerSequence() uint32 { + return o.LedgerCloseMeta.LedgerSequence() +} + +func (o LedgerOperation) LedgerClosedAt() time.Time { + return o.LedgerCloseMeta.LedgerClosedAt() +} + +func (o LedgerOperation) OperationResultCode() string { + var operationResultCode string + operationResults, ok := o.Transaction.Result.Result.OperationResults() + if ok { + operationResultCode = operationResults[o.OperationIndex].Code.String() + } + + return operationResultCode +} + +func (o LedgerOperation) OperationTraceCode() string { + var operationTraceCode string + + operationResults, ok := o.Transaction.Result.Result.OperationResults() + if ok { + operationResultTr, ok := operationResults[o.OperationIndex].GetTr() + if ok { + operationTraceCode, err := operationResultTr.MapOperationResultTr() + if err != nil { + panic(err) + } + return operationTraceCode + } + } + + return operationTraceCode +} + +func (o LedgerOperation) OperationDetails() (map[string]interface{}, error) { + details := map[string]interface{}{} + + switch o.Operation.Body.Type { + case xdr.OperationTypeCreateAccount: + details, err := o.CreateAccountDetails() + if err != nil { + return details, err + } + case xdr.OperationTypePayment: + details, err := o.PaymentDetails() + if err != nil { + return details, err + } + case xdr.OperationTypePathPaymentStrictReceive: + details, err := o.PathPaymentStrictReceiveDetails() + if err != nil { + return details, err + } + // same for all other operations + default: + return details, fmt.Errorf("unknown operation type: %s", o.Operation.Body.Type.String()) + } + + return details, nil +} + +func (o LedgerOperation) CreateAccountDetails() (map[string]interface{}, error) { + details := map[string]interface{}{} + op, ok := o.Operation.Body.GetCreateAccountOp() + if !ok { + return details, fmt.Errorf("could not access CreateAccount info for this operation (index %d)", o.OperationIndex) + } + + if err := addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "funder"); err != nil { + return details, err + } + details["account"] = op.Destination.Address() + details["starting_balance"] = xdr.ConvertStroopValueToReal(op.StartingBalance) + + return details, nil +} + +func addAccountAndMuxedAccountDetails(result map[string]interface{}, a xdr.MuxedAccount, prefix string) error { + account_id := a.ToAccountId() + result[prefix] = account_id.Address() + prefix = formatPrefix(prefix) + if a.Type == xdr.CryptoKeyTypeKeyTypeMuxedEd25519 { + muxedAccountAddress, err := a.GetAddress() + if err != nil { + return err + } + result[prefix+"muxed"] = muxedAccountAddress + muxedAccountId, err := a.GetId() + if err != nil { + return err + } + result[prefix+"muxed_id"] = muxedAccountId + } + return nil +} + +func formatPrefix(p string) string { + if p != "" { + p += "_" + } + return p +} + +func (o LedgerOperation) PaymentDetails() (map[string]interface{}, error) { + details := map[string]interface{}{} + op, ok := o.Operation.Body.GetPaymentOp() + if !ok { + return details, fmt.Errorf("could not access Payment info for this operation (index %d)", o.OperationIndex) + } + + if err := addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "from"); err != nil { + return details, err + } + if err := addAccountAndMuxedAccountDetails(details, op.Destination, "to"); err != nil { + return details, err + } + details["amount"] = xdr.ConvertStroopValueToReal(op.Amount) + if err := addAssetDetailsToOperationDetails(details, op.Asset, ""); err != nil { + return details, err + } + + return details, nil +} + +func addAssetDetailsToOperationDetails(result map[string]interface{}, asset xdr.Asset, prefix string) error { + var assetType, code, issuer string + err := asset.Extract(&assetType, &code, &issuer) + if err != nil { + return err + } + + prefix = formatPrefix(prefix) + result[prefix+"asset_type"] = assetType + + if asset.Type == xdr.AssetTypeAssetTypeNative { + result[prefix+"asset_id"] = int64(-5706705804583548011) + return nil + } + + result[prefix+"asset_code"] = code + result[prefix+"asset_issuer"] = issuer + result[prefix+"asset_id"] = farmHashAsset(code, issuer, assetType) + + return nil +} + +func farmHashAsset(assetCode, assetIssuer, assetType string) int64 { + asset := fmt.Sprintf("%s%s%s", assetCode, assetIssuer, assetType) + hash := farm.Fingerprint64([]byte(asset)) + + return int64(hash) +} + +func (o LedgerOperation) PathPaymentStrictReceiveDetails() (map[string]interface{}, error) { + details := map[string]interface{}{} + op, ok := o.Operation.Body.GetPathPaymentStrictReceiveOp() + if !ok { + return details, fmt.Errorf("could not access PathPaymentStrictReceive info for this operation (index %d)", o.OperationIndex) + } + + if err := addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "from"); err != nil { + return details, err + } + if err := addAccountAndMuxedAccountDetails(details, op.Destination, "to"); err != nil { + return details, err + } + details["amount"] = xdr.ConvertStroopValueToReal(op.DestAmount) + details["source_amount"] = amount.String(0) + details["source_max"] = xdr.ConvertStroopValueToReal(op.SendMax) + if err := addAssetDetailsToOperationDetails(details, op.DestAsset, ""); err != nil { + return details, err + } + if err := addAssetDetailsToOperationDetails(details, op.SendAsset, "source"); err != nil { + return details, err + } + + if o.Transaction.Result.Successful() { + allOperationResults, ok := o.Transaction.Result.OperationResults() + if !ok { + return details, fmt.Errorf("could not access any results for this transaction") + } + currentOperationResult := allOperationResults[o.OperationIndex] + resultBody, ok := currentOperationResult.GetTr() + if !ok { + return details, fmt.Errorf("could not access result body for this operation (index %d)", o.OperationIndex) + } + result, ok := resultBody.GetPathPaymentStrictReceiveResult() + if !ok { + return details, fmt.Errorf("could not access PathPaymentStrictReceive result info for this operation (index %d)", o.OperationIndex) + } + details["source_amount"] = xdr.ConvertStroopValueToReal(result.SendAmount()) + } + + details["path"] = transformPath(op.Path) + return details, nil +} + +// Path is a representation of an asset without an ID that forms part of a path in a path payment +type Path struct { + AssetCode string `json:"asset_code"` + AssetIssuer string `json:"asset_issuer"` + AssetType string `json:"asset_type"` +} + +func transformPath(initialPath []xdr.Asset) []Path { + if len(initialPath) == 0 { + return nil + } + var path = make([]Path, 0) + for _, pathAsset := range initialPath { + var assetType, code, issuer string + err := pathAsset.Extract(&assetType, &code, &issuer) + if err != nil { + return nil + } + + path = append(path, Path{ + AssetType: assetType, + AssetIssuer: issuer, + AssetCode: code, + }) + } + return path +} diff --git a/ingest/ledger_transaction.go b/ingest/ledger_transaction.go index 77ca777206..87a618bd08 100644 --- a/ingest/ledger_transaction.go +++ b/ingest/ledger_transaction.go @@ -2,6 +2,7 @@ package ingest import ( "github.com/stellar/go/support/errors" + "github.com/stellar/go/toid" "github.com/stellar/go/xdr" ) @@ -14,9 +15,10 @@ type LedgerTransaction struct { // you know what you are doing. // Use LedgerTransaction.GetChanges() for higher level access to ledger // entry changes. - FeeChanges xdr.LedgerEntryChanges - UnsafeMeta xdr.TransactionMeta - LedgerVersion uint32 + FeeChanges xdr.LedgerEntryChanges + UnsafeMeta xdr.TransactionMeta + LedgerVersion uint32 + LedgerCloseMeta xdr.LedgerCloseMeta } func (t *LedgerTransaction) txInternalError() bool { @@ -155,3 +157,27 @@ func operationChanges(ops []xdr.OperationMeta, index uint32) []Change { func (t *LedgerTransaction) GetDiagnosticEvents() ([]xdr.DiagnosticEvent, error) { return t.UnsafeMeta.GetDiagnosticEvents() } + +func (t *LedgerTransaction) GetOperations() []LedgerOperation { + var ledgerOperations []LedgerOperation + + for i, operation := range t.Envelope.Operations() { + ledgerOperation := LedgerOperation{ + Operation: operation, + OperationIndex: int32(i), + Transaction: *t, + LedgerCloseMeta: t.LedgerCloseMeta, + } + ledgerOperations = append(ledgerOperations, ledgerOperation) + } + + return ledgerOperations +} + +func (t *LedgerTransaction) TransactionID() int64 { + return toid.New(int32(t.LedgerCloseMeta.LedgerSequence()), int32(t.Index), 0).ToInt64() +} + +func (t *LedgerTransaction) TransactionHash() string { + return t.Result.TransactionHash.HexString() +} diff --git a/xdr/account_entry.go b/xdr/account_entry.go index 0649a71bb9..66955fdce1 100644 --- a/xdr/account_entry.go +++ b/xdr/account_entry.go @@ -113,3 +113,50 @@ func (account *AccountEntry) SeqLedger() Uint32 { } return 0 } + +func (account *AccountEntry) AccountID() string { + return account.AccountId.Address() +} + +func (account *AccountEntry) BalanceFloat() float64 { + return ConvertStroopValueToReal(account.Balance) +} + +func (account *AccountEntry) BuyingLiabilities() float64 { + var buyingLiabilities float64 + accountExtensionInfo, V1Found := account.Ext.GetV1() + if V1Found { + return ConvertStroopValueToReal(accountExtensionInfo.Liabilities.Buying) + } + return buyingLiabilities +} + +func (account *AccountEntry) SellingLiabilities() float64 { + var sellingLiabilities float64 + accountExtensionInfo, V1Found := account.Ext.GetV1() + if V1Found { + return ConvertStroopValueToReal(accountExtensionInfo.Liabilities.Selling) + } + return sellingLiabilities +} + +func (account *AccountEntry) SequenceNumber() int64 { + return int64(account.SeqNum) +} + +func (account *AccountEntry) SequenceLedger() int64 { + return int64(account.SeqLedger()) +} + +func (account *AccountEntry) SequenceTime() int64 { + return int64(account.SeqTime()) +} + +func (account *AccountEntry) InflationDestination() string { + var inflationDest string + inflationDestAccountID := account.InflationDest + if inflationDestAccountID != nil { + return inflationDestAccountID.Address() + } + return inflationDest +} diff --git a/xdr/hash.go b/xdr/hash.go index 2a15c18c9c..86aae508ac 100644 --- a/xdr/hash.go +++ b/xdr/hash.go @@ -17,3 +17,7 @@ func (s Hash) Equals(o Hash) bool { } return true } + +func (h Hash) String() string { + return HashToHexString(h) +} diff --git a/xdr/ledger_close_meta.go b/xdr/ledger_close_meta.go index 30e80b2e38..d9536c3428 100644 --- a/xdr/ledger_close_meta.go +++ b/xdr/ledger_close_meta.go @@ -1,7 +1,9 @@ package xdr import ( + "encoding/base64" "fmt" + "time" ) func (l LedgerCloseMeta) LedgerHeaderHistoryEntry() LedgerHeaderHistoryEntry { @@ -156,3 +158,155 @@ func (l LedgerCloseMeta) EvictedPersistentLedgerEntries() ([]LedgerEntry, error) panic(fmt.Sprintf("Unsupported LedgerCloseMeta.V: %d", l.V)) } } + +func (l LedgerCloseMeta) LedgerID() int64 { + return NewID(int32(l.LedgerSequence()), 0, 0).ToInt64() +} + +func (l LedgerCloseMeta) LedgerClosedAt() time.Time { + return time.Unix(l.LedgerCloseTime(), 0).UTC() +} + +func (l LedgerCloseMeta) TotalCoins() int64 { + return int64(l.LedgerHeaderHistoryEntry().Header.TotalCoins) +} + +func (l LedgerCloseMeta) FeePool() int64 { + return int64(l.LedgerHeaderHistoryEntry().Header.FeePool) +} + +func (l LedgerCloseMeta) BaseFee() uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.BaseFee) +} + +func (l LedgerCloseMeta) BaseReserve() uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.BaseReserve) +} + +func (l LedgerCloseMeta) MaxTxSetSize() uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.MaxTxSetSize) +} + +func (l LedgerCloseMeta) SorobanFeeWrite1Kb() (int64, bool) { + lcmV1, ok := l.GetV1() + if ok { + extV1 := lcmV1.Ext.MustV1() + return int64(extV1.SorobanFeeWrite1Kb), true + } + + return 0, false +} + +func (l LedgerCloseMeta) TotalByteSizeOfBucketList() (uint64, bool) { + lcmV1, ok := l.GetV1() + if ok { + return uint64(lcmV1.TotalByteSizeOfBucketList), true + } + + return 0, false +} + +func (l LedgerCloseMeta) NodeID() (string, bool) { + LedgerCloseValueSignature, ok := l.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() + if ok { + nodeID, ok := GetAddress(LedgerCloseValueSignature.NodeId) + if ok { + return nodeID, true + } + } + + return "", false +} + +func (l LedgerCloseMeta) Signature() (string, bool) { + LedgerCloseValueSignature, ok := l.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() + if ok { + return base64.StdEncoding.EncodeToString(LedgerCloseValueSignature.Signature), true + } + + return "", false +} + +func (l LedgerCloseMeta) TransactionProcessing() []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)) + } +} + +func (l LedgerCloseMeta) CountSuccessfulTransactions() int { + var successfulTransactionCount int + results := l.TransactionProcessing() + + for _, result := range results { + if result.Result.Successful() { + successfulTransactionCount++ + } + } + + return successfulTransactionCount +} + +func (l LedgerCloseMeta) CountFailedTransactions() int { + var failedTransactionCount int + results := l.TransactionProcessing() + + for _, result := range results { + if !result.Result.Successful() { + failedTransactionCount++ + } + } + + return failedTransactionCount +} + +func (l LedgerCloseMeta) CountOperations() (operationCount int) { + transactions := l.TransactionEnvelopes() + + for _, transaction := range transactions { + operations := transaction.Operations() + numberOfOps := len(operations) + operationCount += numberOfOps + } + + return +} + +func (l LedgerCloseMeta) CountSuccessfulOperations() (operationCount int) { + results := l.TransactionProcessing() + + for _, result := range results { + if result.Result.Successful() { + operationResults, ok := result.Result.OperationResults() + if !ok { + panic("could not get []OperationResult") + } + + operationCount += len(operationResults) + } + } + + return +} + +func (l LedgerCloseMeta) CountFailedOperations() (operationCount int) { + results := l.TransactionProcessing() + + for _, result := range results { + if !result.Result.Successful() { + operationResults, ok := result.Result.OperationResults() + if !ok { + panic("could not get []OperationResult") + } + + operationCount += len(operationResults) + } + } + + return +} diff --git a/xdr/operation.go b/xdr/operation.go new file mode 100644 index 0000000000..b4c3463e75 --- /dev/null +++ b/xdr/operation.go @@ -0,0 +1,9 @@ +package xdr + +//func (o Operation) Type() int32 { +// return int32(o.Body.Type) +//} +// +//func (o Operation) TypeString() string { +// return operationTypeMap[o.Type()] +//} diff --git a/xdr/operation_result_trace.go b/xdr/operation_result_trace.go new file mode 100644 index 0000000000..2325005d72 --- /dev/null +++ b/xdr/operation_result_trace.go @@ -0,0 +1,76 @@ +package xdr + +import "fmt" + +//func (o Operation) Type() int32 { +// return int32(o.Body.Type) +//} +// +//func (o Operation) TypeString() string { +// return operationTypeMap[o.Type()] +//} + +func (o OperationResultTr) MapOperationResultTr() (string, error) { + var operationTraceDescription string + operationType := o.Type + + switch operationType { + case OperationTypeCreateAccount: + operationTraceDescription = o.CreateAccountResult.Code.String() + case OperationTypePayment: + operationTraceDescription = o.PaymentResult.Code.String() + case OperationTypePathPaymentStrictReceive: + operationTraceDescription = o.PathPaymentStrictReceiveResult.Code.String() + case OperationTypePathPaymentStrictSend: + operationTraceDescription = o.PathPaymentStrictSendResult.Code.String() + case OperationTypeManageBuyOffer: + operationTraceDescription = o.ManageBuyOfferResult.Code.String() + case OperationTypeManageSellOffer: + operationTraceDescription = o.ManageSellOfferResult.Code.String() + case OperationTypeCreatePassiveSellOffer: + operationTraceDescription = o.CreatePassiveSellOfferResult.Code.String() + case OperationTypeSetOptions: + operationTraceDescription = o.SetOptionsResult.Code.String() + case OperationTypeChangeTrust: + operationTraceDescription = o.ChangeTrustResult.Code.String() + case OperationTypeAllowTrust: + operationTraceDescription = o.AllowTrustResult.Code.String() + case OperationTypeAccountMerge: + operationTraceDescription = o.AccountMergeResult.Code.String() + case OperationTypeInflation: + operationTraceDescription = o.InflationResult.Code.String() + case OperationTypeManageData: + operationTraceDescription = o.ManageDataResult.Code.String() + case OperationTypeBumpSequence: + operationTraceDescription = o.BumpSeqResult.Code.String() + case OperationTypeCreateClaimableBalance: + operationTraceDescription = o.CreateClaimableBalanceResult.Code.String() + case OperationTypeClaimClaimableBalance: + operationTraceDescription = o.ClaimClaimableBalanceResult.Code.String() + case OperationTypeBeginSponsoringFutureReserves: + operationTraceDescription = o.BeginSponsoringFutureReservesResult.Code.String() + case OperationTypeEndSponsoringFutureReserves: + operationTraceDescription = o.EndSponsoringFutureReservesResult.Code.String() + case OperationTypeRevokeSponsorship: + operationTraceDescription = o.RevokeSponsorshipResult.Code.String() + case OperationTypeClawback: + operationTraceDescription = o.ClawbackResult.Code.String() + case OperationTypeClawbackClaimableBalance: + operationTraceDescription = o.ClawbackClaimableBalanceResult.Code.String() + case OperationTypeSetTrustLineFlags: + operationTraceDescription = o.SetTrustLineFlagsResult.Code.String() + case OperationTypeLiquidityPoolDeposit: + operationTraceDescription = o.LiquidityPoolDepositResult.Code.String() + case OperationTypeLiquidityPoolWithdraw: + operationTraceDescription = o.LiquidityPoolWithdrawResult.Code.String() + case OperationTypeInvokeHostFunction: + operationTraceDescription = o.InvokeHostFunctionResult.Code.String() + case OperationTypeExtendFootprintTtl: + operationTraceDescription = o.ExtendFootprintTtlResult.Code.String() + case OperationTypeRestoreFootprint: + operationTraceDescription = o.RestoreFootprintResult.Code.String() + default: + return operationTraceDescription, fmt.Errorf("unknown operation type: %s", o.Type.String()) + } + return operationTraceDescription, nil +} diff --git a/xdr/utils.go b/xdr/utils.go new file mode 100644 index 0000000000..f9643e0c24 --- /dev/null +++ b/xdr/utils.go @@ -0,0 +1,97 @@ +package xdr + +import ( + "encoding/hex" + "math/big" + + "github.com/stellar/go/strkey" +) + +// HashToHexString is utility function that converts and xdr.Hash type to a hex string +func HashToHexString(inputHash 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 NodeId) (string, bool) { + switch nodeID.Type { + case 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 + } +} + +func ConvertStroopValueToReal(input Int64) float64 { + output, _ := big.NewRat(int64(input), int64(10000000)).Float64() + return output +}