From a1f0f73a4090d63120b59ef9a87be6fe8f22bd5c Mon Sep 17 00:00:00 2001 From: Simon Chow Date: Wed, 8 Jan 2025 17:33:12 -0500 Subject: [PATCH] xdrill for ledgerCloseMeta --- exp/xdrill/ledger.go | 248 ++++++++++++++++++++++++++++++++++++++ exp/xdrill/ledger_test.go | 168 ++++++++++++++++++++++++++ exp/xdrill/utils/utils.go | 92 ++++++++++++++ go.mod | 1 + go.sum | 2 + 5 files changed, 511 insertions(+) create mode 100644 exp/xdrill/ledger.go create mode 100644 exp/xdrill/ledger_test.go create mode 100644 exp/xdrill/utils/utils.go diff --git a/exp/xdrill/ledger.go b/exp/xdrill/ledger.go new file mode 100644 index 0000000000..3095094d85 --- /dev/null +++ b/exp/xdrill/ledger.go @@ -0,0 +1,248 @@ +package xdrill + +import ( + "encoding/base64" + "fmt" + "time" + + "github.com/stellar/go/exp/xdrill/utils" + "github.com/stellar/go/keypair" + "github.com/stellar/go/txnbuild" + "github.com/stellar/go/xdr" +) + +type Ledger struct { + ledger *xdr.LedgerCloseMeta +} + +func (l Ledger) Sequence() uint32 { + return uint32(l.ledger.LedgerHeaderHistoryEntry().Header.LedgerSeq) +} + +func (l Ledger) ID() int64 { + return utils.NewID(int32(l.Sequence()), 0, 0).ToInt64() +} + +func (l Ledger) Hash() string { + return utils.HashToHexString(l.ledger.LedgerHeaderHistoryEntry().Hash) +} + +func (l Ledger) PreviousHash() string { + return utils.HashToHexString(l.ledger.PreviousLedgerHash()) +} + +func (l Ledger) CloseTime() int64 { + return l.ledger.LedgerCloseTime() +} + +func (l Ledger) ClosedAt() time.Time { + return time.Unix(l.CloseTime(), 0).UTC() +} + +func (l Ledger) TotalCoins() int64 { + return int64(l.ledger.LedgerHeaderHistoryEntry().Header.TotalCoins) +} + +func (l Ledger) FeePool() int64 { + return int64(l.ledger.LedgerHeaderHistoryEntry().Header.FeePool) +} + +func (l Ledger) BaseFee() uint32 { + return uint32(l.ledger.LedgerHeaderHistoryEntry().Header.BaseFee) +} + +func (l Ledger) BaseReserve() uint32 { + return uint32(l.ledger.LedgerHeaderHistoryEntry().Header.BaseReserve) +} + +func (l Ledger) MaxTxSetSize() uint32 { + return uint32(l.ledger.LedgerHeaderHistoryEntry().Header.MaxTxSetSize) +} + +func (l Ledger) LedgerVersion() uint32 { + return uint32(l.ledger.LedgerHeaderHistoryEntry().Header.LedgerVersion) +} + +func (l Ledger) SorobanFeeWrite1Kb() (int64, bool) { + lcmV1, ok := l.ledger.GetV1() + if !ok { + return 0, false + } + + extV1, ok := lcmV1.Ext.GetV1() + if !ok { + return 0, false + } + + return int64(extV1.SorobanFeeWrite1Kb), true +} + +func (l Ledger) TotalByteSizeOfBucketList() (uint64, bool) { + lcmV1, ok := l.ledger.GetV1() + if !ok { + return 0, false + } + + return uint64(lcmV1.TotalByteSizeOfBucketList), true +} + +func (l Ledger) NodeID() (string, bool) { + LedgerCloseValueSignature, ok := l.ledger.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() + if !ok { + return "", false + + } + nodeID, ok := utils.GetAddress(LedgerCloseValueSignature.NodeId) + if !ok { + return "", false + } + + return nodeID, true +} + +func (l Ledger) Signature() (string, bool) { + LedgerCloseValueSignature, ok := l.ledger.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() + if !ok { + return "", false + } + + return base64.StdEncoding.EncodeToString(LedgerCloseValueSignature.Signature), true +} + +// Add docstring to larger, more complicated functions +func (l Ledger) TransactionCounts() (successTxCount, failedTxCount int32, ok bool) { + var results []xdr.TransactionResultMeta + + transactions := getTransactionSet(l) + switch l.ledger.V { + case 0: + results = l.ledger.V0.TxProcessing + case 1: + results = l.ledger.V1.TxProcessing + } + txCount := len(transactions) + if txCount != len(results) { + return 0, 0, false + } + + for i := 0; i < txCount; i++ { + if results[i].Result.Successful() { + successTxCount++ + } else { + failedTxCount++ + } + } + + return successTxCount, failedTxCount, true +} + +// Add docstring to larger, more complicated functions +func (l Ledger) OperationCounts() (operationCount, txSetOperationCount int32, ok bool) { + var results []xdr.TransactionResultMeta + + transactions := getTransactionSet(l) + switch l.ledger.V { + case 0: + results = l.ledger.V0.TxProcessing + case 1: + results = l.ledger.V1.TxProcessing + } + + txCount := len(transactions) + if txCount != len(results) { + return 0, 0, false + } + + for i := 0; i < txCount; i++ { + operations := transactions[i].Operations() + numberOfOps := int32(len(operations)) + txSetOperationCount += numberOfOps + + // for successful transactions, the operation count is based on the operations results slice + if results[i].Result.Successful() { + operationResults, ok := results[i].Result.OperationResults() + if !ok { + return 0, 0, false + } + + operationCount += int32(len(operationResults)) + } + + } + + return operationCount, txSetOperationCount, true +} + +func getTransactionSet(l Ledger) (transactionProcessing []xdr.TransactionEnvelope) { + switch l.ledger.V { + case 0: + return l.ledger.V0.TxSet.Txs + case 1: + switch l.ledger.V1.TxSet.V { + case 0: + return getTransactionPhase(l.ledger.V1.TxSet.V1TxSet.Phases) + default: + panic(fmt.Sprintf("unsupported LedgerCloseMeta.V1.TxSet.V: %d", l.ledger.V1.TxSet.V)) + } + default: + panic(fmt.Sprintf("unsupported LedgerCloseMeta.V: %d", l.ledger.V)) + } +} + +func getTransactionPhase(transactionPhase []xdr.TransactionPhase) (transactionEnvelope []xdr.TransactionEnvelope) { + transactionSlice := []xdr.TransactionEnvelope{} + for _, phase := range transactionPhase { + switch phase.V { + case 0: + components := phase.MustV0Components() + for _, component := range components { + switch component.Type { + case 0: + transactionSlice = append(transactionSlice, component.TxsMaybeDiscountedFee.Txs...) + + default: + panic(fmt.Sprintf("unsupported TxSetComponentType: %d", component.Type)) + } + + } + default: + panic(fmt.Sprintf("unsupported TransactionPhase.V: %d", phase.V)) + } + } + + return transactionSlice +} + +func CreateSampleTx(sequence int64, operationCount int) xdr.TransactionEnvelope { + kp, err := keypair.Random() + PanicOnError(err) + + operations := []txnbuild.Operation{} + operationType := &txnbuild.BumpSequence{ + BumpTo: 0, + } + for i := 0; i < operationCount; i++ { + operations = append(operations, operationType) + } + + sourceAccount := txnbuild.NewSimpleAccount(kp.Address(), int64(0)) + tx, err := txnbuild.NewTransaction( + txnbuild.TransactionParams{ + SourceAccount: &sourceAccount, + Operations: operations, + BaseFee: txnbuild.MinBaseFee, + Preconditions: txnbuild.Preconditions{TimeBounds: txnbuild.NewInfiniteTimeout()}, + }, + ) + PanicOnError(err) + + env := tx.ToXDR() + return env +} + +// PanicOnError is a function that panics if the provided error is not nil +func PanicOnError(err error) { + if err != nil { + panic(err) + } +} diff --git a/exp/xdrill/ledger_test.go b/exp/xdrill/ledger_test.go new file mode 100644 index 0000000000..febdf65d45 --- /dev/null +++ b/exp/xdrill/ledger_test.go @@ -0,0 +1,168 @@ +package xdrill + +import ( + "testing" + "time" + + "github.com/stellar/go/xdr" + "github.com/stretchr/testify/assert" +) + +func TestLedger(t *testing.T) { + ledger := Ledger{ + ledger: ledgerTestInput(), + } + + assert.Equal(t, uint32(30578981), ledger.Sequence()) + assert.Equal(t, int64(131335723340005376), ledger.ID()) + assert.Equal(t, "26932dc4d84b5fabe9ae744cb43ce4c6daccf98c86a991b2a14945b1adac4d59", ledger.Hash()) + assert.Equal(t, "f63c15d0eaf48afbd751a4c4dfade54a3448053c47c5a71d622668ae0cc2a208", ledger.PreviousHash()) + assert.Equal(t, int64(1594584547), ledger.CloseTime()) + assert.Equal(t, time.Time(time.Date(2020, time.July, 12, 20, 9, 7, 0, time.UTC)), ledger.ClosedAt()) + assert.Equal(t, int64(1054439020873472865), ledger.TotalCoins()) + assert.Equal(t, int64(18153766209161), ledger.FeePool()) + assert.Equal(t, uint32(100), ledger.BaseFee()) + assert.Equal(t, uint32(5000000), ledger.BaseReserve()) + assert.Equal(t, uint32(1000), ledger.MaxTxSetSize()) + assert.Equal(t, uint32(13), ledger.LedgerVersion()) + + var ok bool + + var freeWrite int64 + freeWrite, ok = ledger.SorobanFeeWrite1Kb() + assert.Equal(t, true, ok) + assert.Equal(t, int64(12), freeWrite) + + var bucketSize uint64 + bucketSize, ok = ledger.TotalByteSizeOfBucketList() + assert.Equal(t, true, ok) + assert.Equal(t, uint64(56), bucketSize) + + var nodeID string + nodeID, ok = ledger.NodeID() + assert.Equal(t, true, ok) + assert.Equal(t, "GARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA76O", nodeID) + + var signature string + signature, ok = ledger.Signature() + assert.Equal(t, true, ok) + assert.Equal(t, "", signature) + + var success int32 + var failed int32 + success, failed, ok = ledger.TransactionCounts() + assert.Equal(t, true, ok) + assert.Equal(t, int32(1), success) + assert.Equal(t, int32(1), failed) + + success, failed, ok = ledger.OperationCounts() + assert.Equal(t, true, ok) + assert.Equal(t, int32(1), success) + assert.Equal(t, int32(13), failed) + +} + +func ledgerTestInput() (lcm *xdr.LedgerCloseMeta) { + lcm = &xdr.LedgerCloseMeta{ + V: 1, + V1: &xdr.LedgerCloseMetaV1{ + Ext: xdr.LedgerCloseMetaExt{ + V: 1, + V1: &xdr.LedgerCloseMetaExtV1{ + SorobanFeeWrite1Kb: xdr.Int64(12), + }, + }, + LedgerHeader: xdr.LedgerHeaderHistoryEntry{ + Hash: xdr.Hash{0x26, 0x93, 0x2d, 0xc4, 0xd8, 0x4b, 0x5f, 0xab, 0xe9, 0xae, 0x74, 0x4c, 0xb4, 0x3c, 0xe4, 0xc6, 0xda, 0xcc, 0xf9, 0x8c, 0x86, 0xa9, 0x91, 0xb2, 0xa1, 0x49, 0x45, 0xb1, 0xad, 0xac, 0x4d, 0x59}, + Header: xdr.LedgerHeader{ + LedgerSeq: 30578981, + TotalCoins: 1054439020873472865, + FeePool: 18153766209161, + BaseFee: 100, + BaseReserve: 5000000, + MaxTxSetSize: 1000, + LedgerVersion: 13, + PreviousLedgerHash: xdr.Hash{0xf6, 0x3c, 0x15, 0xd0, 0xea, 0xf4, 0x8a, 0xfb, 0xd7, 0x51, 0xa4, 0xc4, 0xdf, 0xad, 0xe5, 0x4a, 0x34, 0x48, 0x5, 0x3c, 0x47, 0xc5, 0xa7, 0x1d, 0x62, 0x26, 0x68, 0xae, 0xc, 0xc2, 0xa2, 0x8}, + ScpValue: xdr.StellarValue{ + Ext: xdr.StellarValueExt{ + V: 1, + LcValueSignature: &xdr.LedgerCloseValueSignature{ + NodeId: xdr.NodeId{ + Type: 0, + Ed25519: &xdr.Uint256{34}, + }, + }, + }, + CloseTime: 1594584547, + }, + }, + }, + TotalByteSizeOfBucketList: xdr.Uint64(56), + TxSet: xdr.GeneralizedTransactionSet{ + V: 0, + V1TxSet: &xdr.TransactionSetV1{ + Phases: []xdr.TransactionPhase{ + { + V: 0, + V0Components: &[]xdr.TxSetComponent{ + { + Type: 0, + TxsMaybeDiscountedFee: &xdr.TxSetComponentTxsMaybeDiscountedFee{ + Txs: []xdr.TransactionEnvelope{ + CreateSampleTx(0, 3), + CreateSampleTx(1, 10), + }, + }, + }, + }, + }, + }, + }, + }, + TxProcessing: []xdr.TransactionResultMeta{ + { + Result: xdr.TransactionResultPair{ + Result: xdr.TransactionResult{ + Result: xdr.TransactionResultResult{ + Code: xdr.TransactionResultCodeTxSuccess, + Results: &[]xdr.OperationResult{ + { + Code: xdr.OperationResultCodeOpInner, + Tr: &xdr.OperationResultTr{ + Type: xdr.OperationTypeCreateAccount, + CreateAccountResult: &xdr.CreateAccountResult{ + Code: 0, + }, + }, + }, + }, + }, + }, + }, + }, + { + Result: xdr.TransactionResultPair{ + Result: xdr.TransactionResult{ + Result: xdr.TransactionResultResult{ + Code: xdr.TransactionResultCodeTxFailed, + Results: &[]xdr.OperationResult{ + { + Code: xdr.OperationResultCodeOpInner, + Tr: &xdr.OperationResultTr{ + Type: xdr.OperationTypeCreateAccount, + CreateAccountResult: &xdr.CreateAccountResult{ + Code: 0, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + return lcm +} diff --git a/exp/xdrill/utils/utils.go b/exp/xdrill/utils/utils.go new file mode 100644 index 0000000000..0bc8d30c3e --- /dev/null +++ b/exp/xdrill/utils/utils.go @@ -0,0 +1,92 @@ +package utils + +import ( + "encoding/hex" + + "github.com/stellar/go/strkey" + "github.com/stellar/go/xdr" +) + +// HashToHexString is utility function that converts and xdr.Hash type to a hex string +func HashToHexString(inputHash xdr.Hash) string { + sliceHash := inputHash[:] + hexString := hex.EncodeToString(sliceHash) + return hexString +} + +type ID struct { + LedgerSequence int32 + TransactionOrder int32 + OperationOrder int32 +} + +const ( + // LedgerMask is the bitmask to mask out ledger sequences in a + // TotalOrderID + LedgerMask = (1 << 32) - 1 + // TransactionMask is the bitmask to mask out transaction indexes + TransactionMask = (1 << 20) - 1 + // OperationMask is the bitmask to mask out operation indexes + OperationMask = (1 << 12) - 1 + + // LedgerShift is the number of bits to shift an int64 to target the + // ledger component + LedgerShift = 32 + // TransactionShift is the number of bits to shift an int64 to + // target the transaction component + TransactionShift = 12 + // OperationShift is the number of bits to shift an int64 to target + // the operation component + OperationShift = 0 +) + +// New creates a new total order ID +func NewID(ledger int32, tx int32, op int32) *ID { + return &ID{ + LedgerSequence: ledger, + TransactionOrder: tx, + OperationOrder: op, + } +} + +// ToInt64 converts this struct back into an int64 +func (id ID) ToInt64() (result int64) { + + if id.LedgerSequence < 0 { + panic("invalid ledger sequence") + } + + if id.TransactionOrder > TransactionMask { + panic("transaction order overflow") + } + + if id.OperationOrder > OperationMask { + panic("operation order overflow") + } + + result = result | ((int64(id.LedgerSequence) & LedgerMask) << LedgerShift) + result = result | ((int64(id.TransactionOrder) & TransactionMask) << TransactionShift) + result = result | ((int64(id.OperationOrder) & OperationMask) << OperationShift) + return +} + +// TODO: This should be moved into the go monorepo xdr functions +// Or nodeID should just be an xdr.AccountId but the error message would be incorrect +func GetAddress(nodeID xdr.NodeId) (string, bool) { + switch nodeID.Type { + case xdr.PublicKeyTypePublicKeyTypeEd25519: + ed, ok := nodeID.GetEd25519() + if !ok { + return "", false + } + raw := make([]byte, 32) + copy(raw, ed[:]) + encodedAddress, err := strkey.Encode(strkey.VersionByteAccountID, raw) + if err != nil { + return "", false + } + return encodedAddress, true + default: + return "", false + } +} diff --git a/go.mod b/go.mod index 341c85543e..6d7e5fab5d 100644 --- a/go.mod +++ b/go.mod @@ -58,6 +58,7 @@ require ( require ( github.com/cenkalti/backoff/v4 v4.3.0 + github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da github.com/docker/docker v27.3.1+incompatible github.com/docker/go-connections v0.5.0 github.com/fsouza/fake-gcs-server v1.49.2 diff --git a/go.sum b/go.sum index a3c8ce08fa..b071601459 100644 --- a/go.sum +++ b/go.sum @@ -116,6 +116,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/djherbis/fscache v0.10.1 h1:hDv+RGyvD+UDKyRYuLoVNbuRTnf2SrA2K3VyR1br9lk=