From d222d6e15967fba8cc99a7d31b9f94e2855d0ebd Mon Sep 17 00:00:00 2001 From: Simon Chow Date: Mon, 4 Nov 2024 10:41:14 -0500 Subject: [PATCH] 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 + } +}