Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xdrill functions for ledger entry changes #3

Open
wants to merge 4 commits into
base: 5551/xdrill-operations
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions ingest/change.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import (
"bytes"
"fmt"
"sort"
"time"

"github.com/stellar/go/ingest/ledger"
"github.com/stellar/go/ingest/ledgerentry"
"github.com/stellar/go/support/errors"
"github.com/stellar/go/xdr"
)
Expand Down Expand Up @@ -327,3 +330,128 @@ func (c Change) AccountChangedExceptSigners() (bool, error) {

return !bytes.Equal(preBinary, postBinary), nil
}

func (c Change) ExtractEntry() (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")
Comment on lines +340 to +341
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's also LedgerEntryChangeTypeLedgerEntryState which should probably be handled here instead of bailing out, no? Then no error case at all.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh that's good to know. I think stellar-etl code has been skipping LedgerEntryChangeTypeLedgerEntryState on purpose (or accidentally 😨)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the bool for?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ok bools are usually used to signify if the returned value is actually valid or not. And it being not valid is not an error.

For example think of soroban fees. If you want to get soroban fees for a classic transaction you'd return 0 for the fee value but also false for the bool because there is no real value for the fee

Copy link

@amishas157 amishas157 Jan 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Ideally it should be null but also go weirdly handles null values. So this is the way of handling it. 👍

case xdr.LedgerEntryChangeTypeLedgerEntryCreated, xdr.LedgerEntryChangeTypeLedgerEntryUpdated:
return *c.Post, changeType, false, nil

What's the reason of setting false bool in this case? Isn't it returning valid value?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry in this case the bool is to signify if the entry was deleted or not

}
}

func (c Change) Deleted() bool {
return c.LedgerEntryChangeType() == xdr.LedgerEntryChangeTypeLedgerEntryRemoved
}

func (c Change) ClosedAt() time.Time {
if c.Ledger != nil {
return ledger.ClosedAt(*c.Ledger)
}

return ledger.ClosedAt(c.Transaction.Ledger)
chowbao marked this conversation as resolved.
Show resolved Hide resolved
}

func (c Change) Sequence() uint32 {
if c.Ledger != nil {
return ledger.Sequence(*c.Ledger)
}

return ledger.Sequence(c.Transaction.Ledger)
chowbao marked this conversation as resolved.
Show resolved Hide resolved
}

func (c Change) LastModifiedLedger() (uint32, error) {
chowbao marked this conversation as resolved.
Show resolved Hide resolved
ledgerEntry, _, _, err := c.ExtractEntry()
if err != nil {
return 0, err
}

return uint32(ledgerEntry.LastModifiedLedgerSeq), nil
}

func (c Change) Sponsor() (string, error) {
ledgerEntry, _, _, err := c.ExtractEntry()
if err != nil {
return "", err
}

if ledgerEntry.SponsoringID() == nil {
return "", nil
}

return ledgerEntry.SponsoringID().Address(), nil
}

func (c Change) LedgerKeyHash() (string, error) {
ledgerKey, err := c.LedgerKey()
if err != nil {
return "", err
}

return ledgerKey.MarshalBinaryBase64()
}

func (c Change) EntryDetails(passphrase string) (interface{}, error) {
var err error
var ledgerEntry xdr.LedgerEntry
var details interface{}

ledgerEntry, _, _, err = c.ExtractEntry()
if err != nil {
return nil, err
}

switch ledgerEntry.Data.Type {
case xdr.LedgerEntryTypeAccount:
details, err = ledgerentry.AccountDetails(ledgerEntry.Data.Account)
if err != nil {
return details, err
}
case xdr.LedgerEntryTypeTrustline:
details, err = ledgerentry.TrustlineDetails(ledgerEntry.Data.TrustLine)
if err != nil {
return details, err
}
case xdr.LedgerEntryTypeOffer:
details, err = ledgerentry.OfferDetails(ledgerEntry.Data.Offer)
if err != nil {
return details, err
}
Comment on lines +419 to +421
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can skip all of these checks by just returning details, err at the end since err will be set accordingly

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah makes sense. Is that good coding practice though? I thought it's generally good to return/err when it happens rather than at the end of a function. BUT these are also really small functions anyways so it's probably not a big deal

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depends who you ask 😆 imo omitting it can show "all this function does is switch/case + parse + return"

I also hate Go's pedantry for error verbosity so I like to shorten it when I can

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah just golang things 😢

case xdr.LedgerEntryTypeData:
details, err = ledgerentry.DataDetails(ledgerEntry.Data.Data)
if err != nil {
return details, err
}
case xdr.LedgerEntryTypeClaimableBalance:
details, err = ledgerentry.ClaimableBalanceDetails(ledgerEntry.Data.ClaimableBalance)
if err != nil {
return details, err
}
case xdr.LedgerEntryTypeLiquidityPool:
details, err = ledgerentry.LiquidityPoolDetails(ledgerEntry.Data.LiquidityPool)
if err != nil {
return details, err
}
case xdr.LedgerEntryTypeContractData:
details, err = ledgerentry.ContractDataDetails(passphrase, ledgerEntry.Data.ContractData)
if err != nil {
return details, err
}
case xdr.LedgerEntryTypeContractCode:
details, err = ledgerentry.ContractCodeDetails(ledgerEntry.Data.ContractCode)
if err != nil {
return details, err
}
case xdr.LedgerEntryTypeTtl:
details, err = ledgerentry.TtlDetails(ledgerEntry.Data.Ttl)
if err != nil {
return details, err
}
default:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: missing ConfigSetting ledger entry type, but dunno if you're intentionally leaving off

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did leave that off intentionally. But you're right I should just add it and make it do nothing instead of leaving it completely out

return details, fmt.Errorf("unknown LedgerEntry data type")
}

return details, nil
}
76 changes: 76 additions & 0 deletions ingest/ledgerentry/account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package ledgerentry

import (
"github.com/stellar/go/xdr"
)

type Account struct {
AccountID string `json:"account_id"`
Balance int64 `json:"balance"`
SequenceNumber int64 `json:"sequence_number"`
SequenceLedger uint32 `json:"sequence_ledger"`
SequenceTime int64 `json:"sequence_time"`
NumSubentries uint32 `json:"num_subentries"`
Flags uint32 `json:"flags"`
HomeDomain string `json:"home_domain"`
MasterWeight int32 `json:"master_weight"`
ThresholdLow int32 `json:"threshold_low"`
ThresholdMedium int32 `json:"threshold_medium"`
ThresholdHigh int32 `json:"threshold_high"`
NumSponsored uint32 `json:"num_sponsored"`
NumSponsoring uint32 `json:"num_sponsoring"`
BuyingLiabilities int64 `json:"buying_liabilities"`
SellingLiabilities int64 `json:"selling_liabilities"`
InflationDestination string `json:"inflation_destination"`
Signers []Signers `json:"signers"`
}

type Signers struct {
Address string
Weight int32
Sponsor string
}

func AccountDetails(accountEntry *xdr.AccountEntry) (Account, error) {
account := Account{
AccountID: accountEntry.AccountId.Address(),
SequenceNumber: int64(accountEntry.SeqNum),
SequenceLedger: uint32(accountEntry.SeqLedger()),
SequenceTime: int64(accountEntry.SeqTime()),
NumSubentries: uint32(accountEntry.NumSubEntries),
Flags: uint32(accountEntry.Flags),
HomeDomain: string(accountEntry.HomeDomain),
MasterWeight: int32(accountEntry.MasterKeyWeight()),
ThresholdLow: int32(accountEntry.ThresholdLow()),
ThresholdMedium: int32(accountEntry.ThresholdMedium()),
ThresholdHigh: int32(accountEntry.ThresholdHigh()),
NumSponsored: uint32(accountEntry.NumSponsored()),
NumSponsoring: uint32(accountEntry.NumSponsoring()),
}

if accountEntry.InflationDest != nil {
account.InflationDestination = accountEntry.InflationDest.Address()
}

accountExtensionInfo, ok := accountEntry.Ext.GetV1()
if ok {
account.BuyingLiabilities = int64(accountExtensionInfo.Liabilities.Buying)
account.SellingLiabilities = int64(accountExtensionInfo.Liabilities.Selling)
}

signers := []Signers{}
sponsors := accountEntry.SponsorPerSigner()
for signer, weight := range accountEntry.SignerSummary() {
sponsorDesc := sponsors[signer]

signers = append(signers, Signers{
Address: signer,
Weight: weight,
Sponsor: sponsorDesc.Address(),
})
}

account.Signers = signers

return account, nil
}
62 changes: 62 additions & 0 deletions ingest/ledgerentry/claimable_balance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package ledgerentry

import (
"github.com/stellar/go/xdr"
)

type ClaimableBalance struct {
BalanceID string `json:"balance_id"`
Claimants []Claimant `json:"claimants"`
AssetCode string `json:"asset_code"`
AssetIssuer string `json:"asset_issuer"`
AssetType string `json:"asset_type"`
AssetID int64 `json:"asset_id"`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not seeing where you parse AssetId in ClaimableBalanceDetails. Can we just drop?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup this is a mistake. I will drop AssetID

Amount int64 `json:"amount"`
Flags uint32 `json:"flags"`
}

type Claimant struct {
Destination string `json:"destination"`
Predicate xdr.ClaimPredicate `json:"predicate"`
}

func ClaimableBalanceDetails(claimableBalanceEntry *xdr.ClaimableBalanceEntry) (ClaimableBalance, error) {
claimableBalance := ClaimableBalance{
Amount: int64(claimableBalanceEntry.Amount),
Flags: uint32(claimableBalanceEntry.Flags()),
}

var err error
var balanceID string
balanceID, err = xdr.MarshalBase64(claimableBalanceEntry.BalanceId)
if err != nil {
return ClaimableBalance{}, err
}

claimableBalance.BalanceID = balanceID

var assetType, assetCode, assetIssuer string
err = claimableBalanceEntry.Asset.Extract(&assetType, &assetCode, &assetIssuer)
if err != nil {
return ClaimableBalance{}, err
}

claimableBalance.AssetCode = assetCode
claimableBalance.AssetIssuer = assetIssuer
claimableBalance.AssetType = assetType

var claimants []Claimant
for _, c := range claimableBalanceEntry.Claimants {
switch c.Type {
case 0:
claimants = append(claimants, Claimant{
Destination: c.V0.Destination.Address(),
Predicate: c.V0.Predicate,
})
}
}

claimableBalance.Claimants = claimants

return claimableBalance, nil
}
52 changes: 52 additions & 0 deletions ingest/ledgerentry/contract_code.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package ledgerentry

import (
"fmt"

"github.com/stellar/go/xdr"
)

type ContractCode struct {
ContractCodeHash string `json:"contract_code_hash"`
NInstructions uint32 `json:"n_instructions"`
NFunctions uint32 `json:"n_functions"`
NGlobals uint32 `json:"n_globals"`
NTableEntries uint32 `json:"n_table_entries"`
NTypes uint32 `json:"n_types"`
NDataSegments uint32 `json:"n_data_segments"`
NElemSegments uint32 `json:"n_elem_segments"`
NImports uint32 `json:"n_imports"`
NExports uint32 `json:"n_exports"`
NDataSegmentBytes uint32 `json:"n_data_segment_bytes"`
}

func ContractCodeDetails(contractCodeEntry *xdr.ContractCodeEntry) (ContractCode, error) {
var contractCode ContractCode

switch contractCodeEntry.Ext.V {
case 1:
contractCode.NInstructions = uint32(contractCodeEntry.Ext.V1.CostInputs.NInstructions)
contractCode.NFunctions = uint32(contractCodeEntry.Ext.V1.CostInputs.NFunctions)
contractCode.NGlobals = uint32(contractCodeEntry.Ext.V1.CostInputs.NGlobals)
contractCode.NTableEntries = uint32(contractCodeEntry.Ext.V1.CostInputs.NTableEntries)
contractCode.NTypes = uint32(contractCodeEntry.Ext.V1.CostInputs.NTypes)
contractCode.NDataSegments = uint32(contractCodeEntry.Ext.V1.CostInputs.NDataSegments)
contractCode.NElemSegments = uint32(contractCodeEntry.Ext.V1.CostInputs.NElemSegments)
contractCode.NImports = uint32(contractCodeEntry.Ext.V1.CostInputs.NImports)
contractCode.NExports = uint32(contractCodeEntry.Ext.V1.CostInputs.NExports)
contractCode.NDataSegmentBytes = uint32(contractCodeEntry.Ext.V1.CostInputs.NDataSegmentBytes)
default:
return ContractCode{}, fmt.Errorf("unknown ContractCodeEntry.Ext.V")
}

var err error
var contractCodeHash string
contractCodeHash, err = contractCodeEntry.Hash.MarshalBinaryBase64()
if err != nil {
return ContractCode{}, err
}

contractCode.ContractCodeHash = contractCodeHash

return contractCode, nil
}
Loading
Loading