Skip to main content

Retrieve ledger entry changes within a range

This code invokes the stellar-core binary (as configured in ledgerbackend.CaptiveCoreConfig) to replay ledgers from a given ledger range. It uses the ingest.LedgerChangeReader to read changes from each ledger (xdr.LedgerCloseMeta) and categorizes the changes based on whether they represent created, updated, or deleted ledger entries. The ingest.Change structure is used to capture individual changes, and the code tracks various types of changes such as those related to fees, transactions, or operations.

// Filename: change_entries.go

package main

import (
"context"
"encoding/json"
"fmt"
"github.com/stellar/go/ingest"
"github.com/stellar/go/ingest/ledgerbackend"
"github.com/stellar/go/network"
"github.com/stellar/go/xdr"
"io"
)

func panicIf(err error) {
if err != nil {
panic(fmt.Errorf("An error occurred, panicking: %s\n", err))
}
}

func getChangesFromLedger(passphrase string, ledger xdr.LedgerCloseMeta) []ingest.Change {
changeReader, err := ingest.NewLedgerChangeReaderFromLedgerCloseMeta(passphrase, ledger)
panicIf(err)
defer changeReader.Close()

changes := make([]ingest.Change, 0)
for {
change, err := changeReader.Read()
if err == io.EOF {
break
}
panicIf(err)
changes = append(changes, change)
}
return changes
}

func getLedgers(config ledgerbackend.CaptiveCoreConfig, startingLedger uint32, endLedger uint32) []xdr.LedgerCloseMeta {
captiveCore, err := ledgerbackend.NewCaptive(config)
panicIf(err)

ctx := context.Background()
err = captiveCore.PrepareRange(ctx, ledgerbackend.BoundedRange(startingLedger, endLedger))
panicIf(err)
defer captiveCore.Close()

var ledgers []xdr.LedgerCloseMeta
for ledgerSeq := startingLedger; ledgerSeq <= endLedger; ledgerSeq++ {
ledger, err := captiveCore.GetLedger(ctx, ledgerSeq)
panicIf(err)
ledgers = append(ledgers, ledger)
}
return ledgers
}

type ChangesInfo struct {
LedgerEntriesCreated int32
LedgerEntriesUpdated int32
LedgerEntriesDeleted int32
FeeRelatedChanges int32
TxRelatedChanges int32
OperationRelatedChanges int32
}

func changeCausedBy(change ingest.Change) xdr.LedgerEntryChangeType {
if change.Pre == nil && change.Post != nil {
return xdr.LedgerEntryChangeTypeLedgerEntryCreated
} else if change.Pre != nil && change.Post == nil {
return xdr.LedgerEntryChangeTypeLedgerEntryRemoved
} else if change.Pre != nil && change.Post != nil {
return xdr.LedgerEntryChangeTypeLedgerEntryUpdated
}
// this should not happen
panic(fmt.Errorf("unable to dertermine LedgerEntryChangeType from change"))
}

func main() {
archiveURLs := network.PublicNetworkhistoryArchiveURLs
networkPassphrase := network.PublicNetworkPassphrase
captiveCoreToml, err := ledgerbackend.NewCaptiveCoreToml(ledgerbackend.CaptiveCoreTomlParams{
NetworkPassphrase: networkPassphrase,
HistoryArchiveURLs: archiveURLs,
})
panicIf(err)

config := ledgerbackend.CaptiveCoreConfig{
// Change these based on your environment:
BinaryPath: "/usr/local/bin/stellar-core",
NetworkPassphrase: networkPassphrase,
HistoryArchiveURLs: archiveURLs,
Toml: captiveCoreToml,
}

startingLedger := uint32(28921599) // Replace with your desired ledger number
endLedger := startingLedger + 5

// NOTE: connecting to pubnet and getting ledgers might take a while.
ledgers := getLedgers(config, startingLedger, endLedger)

for seq, ledgerMeta := range ledgers {
fmt.Printf("Processing ledger: %d\n", startingLedger+uint32(seq))
changes := getChangesFromLedger(config.NetworkPassphrase, ledgerMeta)
info := ChangesInfo{}
for _, change := range changes {

/* Uncomment this code to print the change struct
jsonData, err := json.MarshalIndent(change, "", " ")
panicIf(err)
fmt.Println(string(jsonData))
*/

switch changeCausedBy(change) {
case xdr.LedgerEntryChangeTypeLedgerEntryCreated:
info.LedgerEntriesCreated++
case xdr.LedgerEntryChangeTypeLedgerEntryRemoved:
info.LedgerEntriesDeleted++
case xdr.LedgerEntryChangeTypeLedgerEntryUpdated:
info.LedgerEntriesUpdated++
}

switch change.Reason {
case ingest.LedgerEntryChangeReasonFee:
info.FeeRelatedChanges++
case ingest.LedgerEntryChangeReasonTransaction:
info.TxRelatedChanges++
case ingest.LedgerEntryChangeReasonOperation:
info.OperationRelatedChanges++
default:
}

}
jsonData, err := json.MarshalIndent(info, "", " ")
panicIf(err)
fmt.Println(string(jsonData))
}
}

Sample Response:

>> go run ./change_entries.go

// Log lines truncated for clarity

Processing ledger: 28921599
{
"LedgerEntriesCreated": 59,
"LedgerEntriesUpdated": 907,
"LedgerEntriesDeleted": 62,
"FeeRelatedChanges": 227,
"TxRelatedChanges": 227,
"OperationRelatedChanges": 574
}
Processing ledger: 28921600
{
"LedgerEntriesCreated": 58,
"LedgerEntriesUpdated": 679,
"LedgerEntriesDeleted": 58,
"FeeRelatedChanges": 120,
"TxRelatedChanges": 120,
"OperationRelatedChanges": 555
}
Processing ledger: 28921601
{
"LedgerEntriesCreated": 61,
"LedgerEntriesUpdated": 885,
"LedgerEntriesDeleted": 63,
"FeeRelatedChanges": 224,
"TxRelatedChanges": 224,
"OperationRelatedChanges": 561
}
Processing ledger: 28921602
{
"LedgerEntriesCreated": 57,
"LedgerEntriesUpdated": 609,
"LedgerEntriesDeleted": 56,
"FeeRelatedChanges": 106,
"TxRelatedChanges": 106,
"OperationRelatedChanges": 510
}
Processing ledger: 28921603
{
"LedgerEntriesCreated": 62,
"LedgerEntriesUpdated": 922,
"LedgerEntriesDeleted": 59,
"FeeRelatedChanges": 230,
"TxRelatedChanges": 230,
"OperationRelatedChanges": 583
}
Processing ledger: 28921604
{
"LedgerEntriesCreated": 61,
"LedgerEntriesUpdated": 670,
"LedgerEntriesDeleted": 58,
"FeeRelatedChanges": 119,
"TxRelatedChanges": 119,
"OperationRelatedChanges": 551
}