diff --git a/exp/xdrill/ledger/ledger.go b/exp/xdrill/ledger/ledger.go new file mode 100644 index 0000000000..b6cb9f9d48 --- /dev/null +++ b/exp/xdrill/ledger/ledger.go @@ -0,0 +1,204 @@ +package ledger + +import ( + "encoding/base64" + "fmt" + "time" + + "github.com/stellar/go/exp/xdrill/utils" + "github.com/stellar/go/xdr" +) + +func Sequence(l xdr.LedgerCloseMeta) uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.LedgerSeq) +} + +func ID(l xdr.LedgerCloseMeta) int64 { + return utils.NewID(int32(l.LedgerSequence()), 0, 0).ToInt64() +} + +func Hash(l xdr.LedgerCloseMeta) string { + return utils.HashToHexString(l.LedgerHeaderHistoryEntry().Hash) +} + +func PreviousHash(l xdr.LedgerCloseMeta) string { + return utils.HashToHexString(l.PreviousLedgerHash()) +} + +func CloseTime(l xdr.LedgerCloseMeta) int64 { + return l.LedgerCloseTime() +} + +func ClosedAt(l xdr.LedgerCloseMeta) time.Time { + return time.Unix(l.LedgerCloseTime(), 0).UTC() +} + +func TotalCoins(l xdr.LedgerCloseMeta) int64 { + return int64(l.LedgerHeaderHistoryEntry().Header.TotalCoins) +} + +func FeePool(l xdr.LedgerCloseMeta) int64 { + return int64(l.LedgerHeaderHistoryEntry().Header.FeePool) +} + +func BaseFee(l xdr.LedgerCloseMeta) uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.BaseFee) +} + +func BaseReserve(l xdr.LedgerCloseMeta) uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.BaseReserve) +} + +func MaxTxSetSize(l xdr.LedgerCloseMeta) uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.MaxTxSetSize) +} + +func LedgerVersion(l xdr.LedgerCloseMeta) uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.LedgerVersion) +} + +func SorobanFeeWrite1Kb(l xdr.LedgerCloseMeta) *int64 { + lcmV1, ok := l.GetV1() + if !ok { + return nil + } + + extV1, ok := lcmV1.Ext.GetV1() + if !ok { + return nil + } + + result := int64(extV1.SorobanFeeWrite1Kb) + + return &result +} + +func TotalByteSizeOfBucketList(l xdr.LedgerCloseMeta) *uint64 { + lcmV1, ok := l.GetV1() + if !ok { + return nil + } + + result := uint64(lcmV1.TotalByteSizeOfBucketList) + + return &result +} + +func NodeID(l xdr.LedgerCloseMeta) *string { + LedgerCloseValueSignature, ok := l.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() + if !ok { + return nil + + } + nodeID, ok := utils.GetAddress(LedgerCloseValueSignature.NodeId) + if !ok { + return nil + } + + return &nodeID +} + +func Signature(l xdr.LedgerCloseMeta) *string { + LedgerCloseValueSignature, ok := l.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() + if !ok { + return nil + } + + result := base64.StdEncoding.EncodeToString(LedgerCloseValueSignature.Signature) + + return &result +} + +// Add docstring to larger, more complicated functions +func TransactionCounts(l xdr.LedgerCloseMeta) (successTxCount, failedTxCount int32, ok bool) { + var results []xdr.TransactionResultMeta + + transactions := getTransactionSet(l) + results = l.TxProcessing() + txCount := len(transactions) + if txCount != len(results) { + return 0, 0, false + } + + for i := 0; i < txCount; i++ { + if results[i].Result.Successful() { + successTxCount++ + } else { + failedTxCount++ + } + } + + return successTxCount, failedTxCount, true +} + +// Add docstring to larger, more complicated functions +func OperationCounts(l xdr.LedgerCloseMeta) (operationCount, txSetOperationCount int32, ok bool) { + var results []xdr.TransactionResultMeta + + transactions := getTransactionSet(l) + results = l.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 xdr.LedgerCloseMeta) (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/ledger/ledger_test.go b/exp/xdrill/ledger/ledger_test.go new file mode 100644 index 0000000000..7adac0a4cb --- /dev/null +++ b/exp/xdrill/ledger/ledger_test.go @@ -0,0 +1,159 @@ +package ledger + +import ( + "testing" + "time" + + "github.com/stellar/go/exp/xdrill/utils" + "github.com/stellar/go/xdr" + "github.com/stretchr/testify/assert" +) + +func TestLedger(t *testing.T) { + ledger := ledgerTestInput() + + assert.Equal(t, uint32(30578981), Sequence(ledger)) + assert.Equal(t, int64(131335723340005376), ID(ledger)) + assert.Equal(t, "26932dc4d84b5fabe9ae744cb43ce4c6daccf98c86a991b2a14945b1adac4d59", Hash(ledger)) + assert.Equal(t, "f63c15d0eaf48afbd751a4c4dfade54a3448053c47c5a71d622668ae0cc2a208", PreviousHash(ledger)) + assert.Equal(t, int64(1594584547), CloseTime(ledger)) + assert.Equal(t, time.Time(time.Date(2020, time.July, 12, 20, 9, 7, 0, time.UTC)), ClosedAt(ledger)) + assert.Equal(t, int64(1054439020873472865), TotalCoins(ledger)) + assert.Equal(t, int64(18153766209161), FeePool(ledger)) + assert.Equal(t, uint32(100), BaseFee(ledger)) + assert.Equal(t, uint32(5000000), BaseReserve(ledger)) + assert.Equal(t, uint32(1000), MaxTxSetSize(ledger)) + assert.Equal(t, uint32(13), LedgerVersion(ledger)) + + freeWrite := SorobanFeeWrite1Kb(ledger) + assert.Equal(t, int64(12), *freeWrite) + + bucketSize := TotalByteSizeOfBucketList(ledger) + assert.Equal(t, uint64(56), *bucketSize) + + nodeID := NodeID(ledger) + assert.Equal(t, "GARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA76O", *nodeID) + + signature := Signature(ledger) + assert.Equal(t, "9g==", *signature) + + var ok bool + var success int32 + var failed int32 + success, failed, ok = TransactionCounts(ledger) + assert.Equal(t, true, ok) + assert.Equal(t, int32(1), success) + assert.Equal(t, int32(1), failed) + + success, failed, ok = OperationCounts(ledger) + assert.Equal(t, true, ok) + assert.Equal(t, int32(1), success) + assert.Equal(t, int32(13), failed) + +} + +func ledgerTestInput() (lcm xdr.LedgerCloseMeta) { + lcm = xdr.LedgerCloseMeta{ + V: 1, + V1: &xdr.LedgerCloseMetaV1{ + Ext: xdr.LedgerCloseMetaExt{ + V: 1, + V1: &xdr.LedgerCloseMetaExtV1{ + SorobanFeeWrite1Kb: xdr.Int64(12), + }, + }, + LedgerHeader: xdr.LedgerHeaderHistoryEntry{ + Hash: xdr.Hash{0x26, 0x93, 0x2d, 0xc4, 0xd8, 0x4b, 0x5f, 0xab, 0xe9, 0xae, 0x74, 0x4c, 0xb4, 0x3c, 0xe4, 0xc6, 0xda, 0xcc, 0xf9, 0x8c, 0x86, 0xa9, 0x91, 0xb2, 0xa1, 0x49, 0x45, 0xb1, 0xad, 0xac, 0x4d, 0x59}, + Header: xdr.LedgerHeader{ + LedgerSeq: 30578981, + TotalCoins: 1054439020873472865, + FeePool: 18153766209161, + BaseFee: 100, + BaseReserve: 5000000, + MaxTxSetSize: 1000, + LedgerVersion: 13, + PreviousLedgerHash: xdr.Hash{0xf6, 0x3c, 0x15, 0xd0, 0xea, 0xf4, 0x8a, 0xfb, 0xd7, 0x51, 0xa4, 0xc4, 0xdf, 0xad, 0xe5, 0x4a, 0x34, 0x48, 0x5, 0x3c, 0x47, 0xc5, 0xa7, 0x1d, 0x62, 0x26, 0x68, 0xae, 0xc, 0xc2, 0xa2, 0x8}, + ScpValue: xdr.StellarValue{ + Ext: xdr.StellarValueExt{ + V: 1, + LcValueSignature: &xdr.LedgerCloseValueSignature{ + NodeId: xdr.NodeId{ + Type: 0, + Ed25519: &xdr.Uint256{34}, + }, + Signature: []byte{0xf6}, + }, + }, + CloseTime: 1594584547, + }, + }, + }, + TotalByteSizeOfBucketList: xdr.Uint64(56), + TxSet: xdr.GeneralizedTransactionSet{ + V: 0, + V1TxSet: &xdr.TransactionSetV1{ + Phases: []xdr.TransactionPhase{ + { + V: 0, + V0Components: &[]xdr.TxSetComponent{ + { + Type: 0, + TxsMaybeDiscountedFee: &xdr.TxSetComponentTxsMaybeDiscountedFee{ + Txs: []xdr.TransactionEnvelope{ + utils.CreateSampleTx(0, 3), + utils.CreateSampleTx(1, 10), + }, + }, + }, + }, + }, + }, + }, + }, + TxProcessing: []xdr.TransactionResultMeta{ + { + Result: xdr.TransactionResultPair{ + Result: xdr.TransactionResult{ + Result: xdr.TransactionResultResult{ + Code: xdr.TransactionResultCodeTxSuccess, + Results: &[]xdr.OperationResult{ + { + Code: xdr.OperationResultCodeOpInner, + Tr: &xdr.OperationResultTr{ + Type: xdr.OperationTypeCreateAccount, + CreateAccountResult: &xdr.CreateAccountResult{ + Code: 0, + }, + }, + }, + }, + }, + }, + }, + }, + { + Result: xdr.TransactionResultPair{ + Result: xdr.TransactionResult{ + Result: xdr.TransactionResultResult{ + Code: xdr.TransactionResultCodeTxFailed, + Results: &[]xdr.OperationResult{ + { + Code: xdr.OperationResultCodeOpInner, + Tr: &xdr.OperationResultTr{ + Type: xdr.OperationTypeCreateAccount, + CreateAccountResult: &xdr.CreateAccountResult{ + Code: 0, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + return lcm +} diff --git a/exp/xdrill/transaction/transaction.go b/exp/xdrill/transaction/transaction.go new file mode 100644 index 0000000000..15c1f1a664 --- /dev/null +++ b/exp/xdrill/transaction/transaction.go @@ -0,0 +1,359 @@ +package transaction + +import ( + "encoding/base64" + "encoding/hex" + "fmt" + "strconv" + + "github.com/stellar/go/exp/xdrill/ledger" + "github.com/stellar/go/exp/xdrill/utils" + "github.com/stellar/go/ingest" + "github.com/stellar/go/toid" + "github.com/stellar/go/xdr" +) + +func Hash(t ingest.LedgerTransaction) string { + return utils.HashToHexString(t.Result.TransactionHash) +} + +func Index(t ingest.LedgerTransaction) uint32 { + return uint32(t.Index) +} + +func ID(t ingest.LedgerTransaction, l xdr.LedgerCloseMeta) int64 { + return toid.New(int32(ledger.Sequence(l)), int32(t.Index), 0).ToInt64() +} + +func Account(t ingest.LedgerTransaction) (string, error) { + return utils.GetAccountAddressFromMuxedAccount(t.Envelope.SourceAccount()) +} + +func AccountSequence(t ingest.LedgerTransaction) int64 { + return t.Envelope.SeqNum() +} + +func MaxFee(t ingest.LedgerTransaction) uint32 { + return t.Envelope.Fee() +} + +func FeeCharged(t ingest.LedgerTransaction, l xdr.LedgerCloseMeta) *int64 { + // Any Soroban Fee Bump transactions before P21 will need the below logic to calculate the correct feeCharged + // Protocol 20 contained a bug where the feeCharged was incorrectly calculated but was fixed for + // Protocol 21 with https://github.com/stellar/stellar-core/issues/4188 + var result int64 + _, ok := getSorobanData(t) + if ok { + if ledger.LedgerVersion(l) < 21 && t.Envelope.Type == xdr.EnvelopeTypeEnvelopeTypeTxFeeBump { + resourceFeeRefund := SorobanResourceFeeRefund(t) + inclusionFeeCharged := SorobanInclusionFeeCharged(t) + result = int64(t.Result.Result.FeeCharged) - *resourceFeeRefund + *inclusionFeeCharged + return &result + } + } + + result = int64(t.Result.Result.FeeCharged) + + return &result +} + +func OperationCount(t ingest.LedgerTransaction) uint32 { + return uint32(len(t.Envelope.Operations())) +} + +func Memo(t ingest.LedgerTransaction) string { + memoObject := t.Envelope.Memo() + memoContents := "" + switch xdr.MemoType(memoObject.Type) { + case xdr.MemoTypeMemoText: + memoContents = memoObject.MustText() + case xdr.MemoTypeMemoId: + memoContents = strconv.FormatUint(uint64(memoObject.MustId()), 10) + case xdr.MemoTypeMemoHash: + hash := memoObject.MustHash() + memoContents = base64.StdEncoding.EncodeToString(hash[:]) + case xdr.MemoTypeMemoReturn: + hash := memoObject.MustRetHash() + memoContents = base64.StdEncoding.EncodeToString(hash[:]) + } + + return memoContents +} + +func MemoType(t ingest.LedgerTransaction) string { + memoObject := t.Envelope.Memo() + return memoObject.Type.String() +} + +func TimeBounds(t ingest.LedgerTransaction) (*string, error) { + timeBounds := t.Envelope.TimeBounds() + if timeBounds == nil { + return nil, nil + } + + if timeBounds.MaxTime < timeBounds.MinTime && timeBounds.MaxTime != 0 { + return nil, fmt.Errorf("the max time is earlier than the min time") + } + + var result string + if timeBounds.MaxTime == 0 { + result = fmt.Sprintf("[%d,)", timeBounds.MinTime) + return &result, nil + } + + result = fmt.Sprintf("[%d,%d)", timeBounds.MinTime, timeBounds.MaxTime) + + return &result, nil +} + +func LedgerBounds(t ingest.LedgerTransaction) *string { + ledgerBounds := t.Envelope.LedgerBounds() + if ledgerBounds == nil { + return nil + } + + result := fmt.Sprintf("[%d,%d)", int64(ledgerBounds.MinLedger), int64(ledgerBounds.MaxLedger)) + + return &result +} + +func MinSequence(t ingest.LedgerTransaction) *int64 { + return t.Envelope.MinSeqNum() +} + +func MinSequenceAge(t ingest.LedgerTransaction) *int64 { + minSequenceAge := t.Envelope.MinSeqAge() + if minSequenceAge == nil { + return nil + } + + minSequenceAgeInt64 := int64(*minSequenceAge) + return &minSequenceAgeInt64 +} + +func MinSequenceLedgerGap(t ingest.LedgerTransaction) *int64 { + minSequenceLedgerGap := t.Envelope.MinSeqLedgerGap() + result := int64(*minSequenceLedgerGap) + return &result +} + +func getSorobanData(t ingest.LedgerTransaction) (sorobanData xdr.SorobanTransactionData, ok bool) { + switch t.Envelope.Type { + case xdr.EnvelopeTypeEnvelopeTypeTx: + return t.Envelope.V1.Tx.Ext.GetSorobanData() + case xdr.EnvelopeTypeEnvelopeTypeTxFeeBump: + return t.Envelope.FeeBump.Tx.InnerTx.V1.Tx.Ext.GetSorobanData() + } + + return +} + +func SorobanResourceFee(t ingest.LedgerTransaction) *int64 { + sorobanData, ok := getSorobanData(t) + if !ok { + return nil + } + + result := int64(sorobanData.ResourceFee) + return &result +} + +func SorobanResourcesInstructions(t ingest.LedgerTransaction) *uint32 { + sorobanData, ok := getSorobanData(t) + if !ok { + return nil + } + + result := uint32(sorobanData.Resources.Instructions) + return &result +} + +func SorobanResourcesReadBytes(t ingest.LedgerTransaction) *uint32 { + sorobanData, ok := getSorobanData(t) + if !ok { + return nil + } + + result := uint32(sorobanData.Resources.ReadBytes) + return &result +} + +func SorobanResourcesWriteBytes(t ingest.LedgerTransaction) *uint32 { + sorobanData, ok := getSorobanData(t) + if !ok { + return nil + } + + result := uint32(sorobanData.Resources.WriteBytes) + return &result +} + +func InclusionFeeBid(t ingest.LedgerTransaction) *int64 { + resourceFee := SorobanResourceFee(t) + if resourceFee == nil { + return nil + } + + result := int64(t.Envelope.Fee()) - *resourceFee + return &result +} + +func getFeeAccountAddress(t ingest.LedgerTransaction) (feeAccountAddress string) { + switch t.Envelope.Type { + case xdr.EnvelopeTypeEnvelopeTypeTx: + sourceAccount := t.Envelope.SourceAccount() + feeAccountAddress = sourceAccount.Address() + case xdr.EnvelopeTypeEnvelopeTypeTxFeeBump: + feeBumpAccount := t.Envelope.FeeBumpAccount() + feeAccountAddress = feeBumpAccount.Address() + } + + return +} + +func SorobanInclusionFeeCharged(t ingest.LedgerTransaction) *int64 { + resourceFee := SorobanResourceFee(t) + if resourceFee == nil { + return nil + } + + accountBalanceStart, accountBalanceEnd := utils.GetAccountBalanceFromLedgerEntryChanges(t.FeeChanges, getFeeAccountAddress(t)) + initialFeeCharged := accountBalanceStart - accountBalanceEnd + result := initialFeeCharged - *resourceFee + + return &result +} + +func SorobanResourceFeeRefund(t ingest.LedgerTransaction) *int64 { + meta, ok := t.UnsafeMeta.GetV3() + if !ok { + return nil + } + + accountBalanceStart, accountBalanceEnd := utils.GetAccountBalanceFromLedgerEntryChanges(meta.TxChangesAfter, getFeeAccountAddress(t)) + result := accountBalanceEnd - accountBalanceStart + + return &result +} + +func SorobanTotalNonRefundableResourceFeeCharged(t ingest.LedgerTransaction) *int64 { + meta, ok := t.UnsafeMeta.GetV3() + if !ok { + return nil + } + + switch meta.SorobanMeta.Ext.V { + case 1: + result := int64(meta.SorobanMeta.Ext.V1.TotalNonRefundableResourceFeeCharged) + return &result + } + + return nil +} + +func SorobanTotalRefundableResourceFeeCharged(t ingest.LedgerTransaction) *int64 { + meta, ok := t.UnsafeMeta.GetV3() + if !ok { + return nil + } + + switch meta.SorobanMeta.Ext.V { + case 1: + result := int64(meta.SorobanMeta.Ext.V1.TotalRefundableResourceFeeCharged) + return &result + } + + return nil +} + +func SorobanRentFeeCharged(t ingest.LedgerTransaction) *int64 { + meta, ok := t.UnsafeMeta.GetV3() + if !ok { + return nil + } + + switch meta.SorobanMeta.Ext.V { + case 1: + result := int64(meta.SorobanMeta.Ext.V1.RentFeeCharged) + return &result + } + + return nil +} + +func ResultCode(t ingest.LedgerTransaction) string { + return t.Result.Result.Result.Code.String() +} + +func Signers(t ingest.LedgerTransaction) (signers []string) { + if t.Envelope.IsFeeBump() { + signers, _ = utils.GetTxSigners(t.Envelope.FeeBump.Signatures) + return + } + + signers, _ = utils.GetTxSigners(t.Envelope.Signatures()) + + return +} + +func AccountMuxed(t ingest.LedgerTransaction) *string { + sourceAccount := t.Envelope.SourceAccount() + if sourceAccount.Type != xdr.CryptoKeyTypeKeyTypeMuxedEd25519 { + return nil + } + + result := sourceAccount.Address() + + return &result +} + +func FeeAccount(t ingest.LedgerTransaction) *string { + if !t.Envelope.IsFeeBump() { + return nil + } + + feeBumpAccount := t.Envelope.FeeBumpAccount() + feeAccount := feeBumpAccount.ToAccountId() + result := feeAccount.Address() + + return &result +} + +func FeeAccountMuxed(t ingest.LedgerTransaction) *string { + if !t.Envelope.IsFeeBump() { + return nil + } + + feeBumpAccount := t.Envelope.FeeBumpAccount() + if feeBumpAccount.Type != xdr.CryptoKeyTypeKeyTypeMuxedEd25519 { + return nil + } + + result := feeBumpAccount.Address() + + return &result +} + +func InnerTransactionHash(t ingest.LedgerTransaction) *string { + if !t.Envelope.IsFeeBump() { + return nil + } + + innerHash := t.Result.InnerHash() + result := hex.EncodeToString(innerHash[:]) + + return &result +} + +func NewMaxFee(t ingest.LedgerTransaction) *uint32 { + if !t.Envelope.IsFeeBump() { + return nil + } + + newMaxFee := uint32(t.Envelope.FeeBumpFee()) + return &newMaxFee +} + +func Successful(t ingest.LedgerTransaction) bool { + return t.Result.Successful() +} diff --git a/exp/xdrill/transaction/transaction_test.go b/exp/xdrill/transaction/transaction_test.go new file mode 100644 index 0000000000..9dc36aa9ef --- /dev/null +++ b/exp/xdrill/transaction/transaction_test.go @@ -0,0 +1,242 @@ +package transaction + +import ( + "testing" + + "github.com/stellar/go/ingest" + "github.com/stellar/go/xdr" + "github.com/stretchr/testify/assert" +) + +func TestTransaction(t *testing.T) { + ledger := ledgerTestInput() + transaction := transactionTestInput() + + assert.Equal(t, "1122330000000000000000000000000000000000000000000000000000000000", Hash(transaction)) + assert.Equal(t, uint32(1), Index(transaction)) + assert.Equal(t, int64(131335723340009472), ID(transaction, ledger)) + + var err error + var account string + account, err = Account(transaction) + assert.Equal(t, nil, err) + assert.Equal(t, "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK", account) + + assert.Equal(t, int64(30578981), AccountSequence(transaction)) + assert.Equal(t, uint32(4560), MaxFee(transaction)) + + feeCharged := FeeCharged(transaction, ledger) + assert.Equal(t, int64(789), *feeCharged) + + assert.Equal(t, uint32(3), OperationCount(transaction)) + assert.Equal(t, "test memo", Memo(transaction)) + assert.Equal(t, "MemoTypeMemoText", MemoType(transaction)) + + var timeBounds *string + timeBounds, err = TimeBounds(transaction) + assert.Equal(t, nil, err) + assert.Equal(t, "[1,10)", *timeBounds) + + ledgerBounds := LedgerBounds(transaction) + assert.Equal(t, "[2,20)", *ledgerBounds) + + minSequence := MinSequence(transaction) + assert.Equal(t, int64(123), *minSequence) + + minSequenceAge := MinSequenceAge(transaction) + assert.Equal(t, int64(456), *minSequenceAge) + + minSequenceLedgerGap := MinSequenceLedgerGap(transaction) + assert.Equal(t, int64(789), *minSequenceLedgerGap) + + sorobanResourceFee := SorobanResourceFee(transaction) + assert.Equal(t, int64(1234), *sorobanResourceFee) + + sorobanResourcesInstructions := SorobanResourcesInstructions(transaction) + assert.Equal(t, uint32(123), *sorobanResourcesInstructions) + + sorobanResourcesReadBytes := SorobanResourcesReadBytes(transaction) + assert.Equal(t, uint32(456), *sorobanResourcesReadBytes) + + sorobanResourcesWriteBytes := SorobanResourcesWriteBytes(transaction) + assert.Equal(t, uint32(789), *sorobanResourcesWriteBytes) + + inclusionFeeBid := InclusionFeeBid(transaction) + assert.Equal(t, int64(3326), *inclusionFeeBid) + + sorobanInclusionFeeCharged := SorobanInclusionFeeCharged(transaction) + assert.Equal(t, int64(-1234), *sorobanInclusionFeeCharged) + + sorobanResourceFeeRefund := SorobanResourceFeeRefund(transaction) + assert.Equal(t, int64(0), *sorobanResourceFeeRefund) + + sorobanTotalNonRefundableResourceFeeCharged := SorobanTotalNonRefundableResourceFeeCharged(transaction) + assert.Equal(t, int64(321), *sorobanTotalNonRefundableResourceFeeCharged) + + sorobanTotalRefundableResourceFeeCharged := SorobanTotalRefundableResourceFeeCharged(transaction) + assert.Equal(t, int64(123), *sorobanTotalRefundableResourceFeeCharged) + + sorobanRentFeeCharged := SorobanRentFeeCharged(transaction) + assert.Equal(t, int64(456), *sorobanRentFeeCharged) + + assert.Equal(t, "TransactionResultCodeTxSuccess", ResultCode(transaction)) + assert.Equal(t, []string{"GAISFR7R"}, Signers(transaction)) + + accountMuxed := AccountMuxed(transaction) + assert.Equal(t, "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I", *accountMuxed) + + feeAccount := FeeAccount(transaction) + assert.Equal(t, (*string)(nil), feeAccount) + + feeAccountMuxed := FeeAccountMuxed(transaction) + assert.Equal(t, (*string)(nil), feeAccountMuxed) + + innerTransactionHash := InnerTransactionHash(transaction) + assert.Equal(t, (*string)(nil), innerTransactionHash) + + assert.Equal(t, (*uint32)(nil), NewMaxFee(transaction)) + assert.Equal(t, true, Successful(transaction)) +} + +func ledgerTestInput() (lcm xdr.LedgerCloseMeta) { + lcm = xdr.LedgerCloseMeta{ + V: 1, + V1: &xdr.LedgerCloseMetaV1{ + LedgerHeader: xdr.LedgerHeaderHistoryEntry{ + Header: xdr.LedgerHeader{ + LedgerSeq: 30578981, + LedgerVersion: 22, + }, + }, + }, + } + + return lcm +} + +func transactionTestInput() ingest.LedgerTransaction { + ed25519 := xdr.Uint256([32]byte{0x11, 0x22, 0x33}) + muxedAccount := xdr.MuxedAccount{ + Type: 256, + Ed25519: &ed25519, + Med25519: &xdr.MuxedAccountMed25519{ + Id: xdr.Uint64(123), + Ed25519: ed25519, + }, + } + + memoText := "test memo" + minSeqNum := xdr.SequenceNumber(123) + + transaction := ingest.LedgerTransaction{ + Index: 1, + Envelope: xdr.TransactionEnvelope{ + Type: xdr.EnvelopeTypeEnvelopeTypeTx, + V1: &xdr.TransactionV1Envelope{ + Signatures: []xdr.DecoratedSignature{ + { + Signature: []byte{0x11, 0x22}, + }, + }, + Tx: xdr.Transaction{ + SourceAccount: muxedAccount, + SeqNum: xdr.SequenceNumber(30578981), + Fee: xdr.Uint32(4560), + Operations: []xdr.Operation{ + { + SourceAccount: &muxedAccount, + Body: xdr.OperationBody{}, + }, + { + SourceAccount: &muxedAccount, + Body: xdr.OperationBody{}, + }, + { + SourceAccount: &muxedAccount, + Body: xdr.OperationBody{}, + }, + }, + Memo: xdr.Memo{ + Type: xdr.MemoTypeMemoText, + Text: &memoText, + }, + Cond: xdr.Preconditions{ + Type: 2, + V2: &xdr.PreconditionsV2{ + TimeBounds: &xdr.TimeBounds{ + MinTime: xdr.TimePoint(1), + MaxTime: xdr.TimePoint(10), + }, + LedgerBounds: &xdr.LedgerBounds{ + MinLedger: 2, + MaxLedger: 20, + }, + MinSeqNum: &minSeqNum, + MinSeqAge: 456, + MinSeqLedgerGap: 789, + }, + }, + Ext: xdr.TransactionExt{ + V: 1, + SorobanData: &xdr.SorobanTransactionData{ + Resources: xdr.SorobanResources{ + Instructions: 123, + ReadBytes: 456, + WriteBytes: 789, + }, + ResourceFee: 1234, + }, + }, + }, + }, + }, + Result: xdr.TransactionResultPair{ + TransactionHash: xdr.Hash{0x11, 0x22, 0x33}, + Result: xdr.TransactionResult{ + FeeCharged: xdr.Int64(789), + Result: xdr.TransactionResultResult{ + Code: 0, + }, + }, + }, + FeeChanges: xdr.LedgerEntryChanges{ + { + Type: xdr.LedgerEntryChangeTypeLedgerEntryState, + State: &xdr.LedgerEntry{ + Data: xdr.LedgerEntryData{ + Type: xdr.LedgerEntryTypeAccount, + Account: &xdr.AccountEntry{ + AccountId: xdr.AccountId{ + Type: 0, + Ed25519: &ed25519, + }, + Balance: 1000, + }, + }, + }, + }, + {}, + }, + UnsafeMeta: xdr.TransactionMeta{ + V: 3, + V3: &xdr.TransactionMetaV3{ + TxChangesAfter: xdr.LedgerEntryChanges{}, + SorobanMeta: &xdr.SorobanTransactionMeta{ + Ext: xdr.SorobanTransactionMetaExt{ + V: 1, + V1: &xdr.SorobanTransactionMetaExtV1{ + TotalNonRefundableResourceFeeCharged: 321, + TotalRefundableResourceFeeCharged: 123, + RentFeeCharged: 456, + }, + }, + }, + }, + }, + LedgerVersion: 22, + Ledger: xdr.LedgerCloseMeta{}, + Hash: xdr.Hash{}, + } + + return transaction +} diff --git a/exp/xdrill/utils/utils.go b/exp/xdrill/utils/utils.go new file mode 100644 index 0000000000..b5fc44329e --- /dev/null +++ b/exp/xdrill/utils/utils.go @@ -0,0 +1,179 @@ +package utils + +import ( + "encoding/hex" + + "github.com/stellar/go/keypair" + "github.com/stellar/go/strkey" + "github.com/stellar/go/txnbuild" + "github.com/stellar/go/xdr" +) + +// 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 + } +} + +func CreateSampleTx(sequence int64, operationCount int) xdr.TransactionEnvelope { + kp, err := keypair.Random() + PanicOnError(err) + + operations := []txnbuild.Operation{} + operationType := &txnbuild.BumpSequence{ + BumpTo: 0, + } + for i := 0; i < operationCount; i++ { + operations = append(operations, operationType) + } + + sourceAccount := txnbuild.NewSimpleAccount(kp.Address(), int64(0)) + tx, err := txnbuild.NewTransaction( + txnbuild.TransactionParams{ + SourceAccount: &sourceAccount, + Operations: operations, + BaseFee: txnbuild.MinBaseFee, + Preconditions: txnbuild.Preconditions{TimeBounds: txnbuild.NewInfiniteTimeout()}, + }, + ) + PanicOnError(err) + + env := tx.ToXDR() + return env +} + +// PanicOnError is a function that panics if the provided error is not nil +func PanicOnError(err error) { + if err != nil { + panic(err) + } +} + +// GetAccountAddressFromMuxedAccount takes in a muxed account and returns the address of the account +func GetAccountAddressFromMuxedAccount(account xdr.MuxedAccount) (string, error) { + providedID := account.ToAccountId() + pointerToID := &providedID + return pointerToID.GetAddress() +} + +func GetAccountBalanceFromLedgerEntryChanges(changes xdr.LedgerEntryChanges, sourceAccountAddress string) (int64, int64) { + var accountBalanceStart int64 + var accountBalanceEnd int64 + + for _, change := range changes { + switch change.Type { + case xdr.LedgerEntryChangeTypeLedgerEntryUpdated: + accountEntry, ok := change.Updated.Data.GetAccount() + if !ok { + continue + } + + if accountEntry.AccountId.Address() == sourceAccountAddress { + accountBalanceEnd = int64(accountEntry.Balance) + } + case xdr.LedgerEntryChangeTypeLedgerEntryState: + accountEntry, ok := change.State.Data.GetAccount() + if !ok { + continue + } + + if accountEntry.AccountId.Address() == sourceAccountAddress { + accountBalanceStart = int64(accountEntry.Balance) + } + } + } + + return accountBalanceStart, accountBalanceEnd +} + +func GetTxSigners(xdrSignatures []xdr.DecoratedSignature) ([]string, error) { + signers := make([]string, len(xdrSignatures)) + + for i, sig := range xdrSignatures { + signerAccount, err := strkey.Encode(strkey.VersionByteAccountID, sig.Signature) + if err != nil { + return nil, err + } + signers[i] = signerAccount + } + + return signers, nil +} diff --git a/xdr/ledger_close_meta.go b/xdr/ledger_close_meta.go index 30e80b2e38..bcb4fd9645 100644 --- a/xdr/ledger_close_meta.go +++ b/xdr/ledger_close_meta.go @@ -156,3 +156,15 @@ func (l LedgerCloseMeta) EvictedPersistentLedgerEntries() ([]LedgerEntry, error) panic(fmt.Sprintf("Unsupported LedgerCloseMeta.V: %d", l.V)) } } + +// TxProcessing returns the TransactionResultMeta in this ledger +func (l LedgerCloseMeta) TxProcessing() []TransactionResultMeta { + switch l.V { + case 0: + return l.MustV0().TxProcessing + case 1: + return l.MustV1().TxProcessing + default: + panic(fmt.Sprintf("Unsupported LedgerCloseMeta.V: %d", l.V)) + } +}