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 +}