github.com/stellar/stellar-etl@v1.0.1-0.20240312145900-4874b6bf2b89/internal/utils/main.go (about) 1 package utils 2 3 import ( 4 "context" 5 "encoding/hex" 6 "errors" 7 "fmt" 8 "math/big" 9 "time" 10 11 "github.com/spf13/pflag" 12 13 "github.com/stellar/go/hash" 14 "github.com/stellar/go/historyarchive" 15 "github.com/stellar/go/ingest" 16 "github.com/stellar/go/ingest/ledgerbackend" 17 "github.com/stellar/go/keypair" 18 "github.com/stellar/go/network" 19 "github.com/stellar/go/support/storage" 20 "github.com/stellar/go/txnbuild" 21 "github.com/stellar/go/xdr" 22 ) 23 24 // PanicOnError is a function that panics if the provided error is not nil 25 func PanicOnError(err error) { 26 if err != nil { 27 panic(err) 28 } 29 } 30 31 // HashToHexString is utility function that converts and xdr.Hash type to a hex string 32 func HashToHexString(inputHash xdr.Hash) string { 33 sliceHash := inputHash[:] 34 hexString := hex.EncodeToString(sliceHash) 35 return hexString 36 } 37 38 // TimePointToUTCTimeStamp takes in an xdr TimePoint and converts it to a time.Time struct in UTC. It returns an error for negative timepoints 39 func TimePointToUTCTimeStamp(providedTime xdr.TimePoint) (time.Time, error) { 40 intTime := int64(providedTime) 41 if intTime < 0 { 42 return time.Now(), errors.New("the timepoint is negative") 43 } 44 return time.Unix(intTime, 0).UTC(), nil 45 } 46 47 // GetAccountAddressFromMuxedAccount takes in a muxed account and returns the address of the account 48 func GetAccountAddressFromMuxedAccount(account xdr.MuxedAccount) (string, error) { 49 providedID := account.ToAccountId() 50 pointerToID := &providedID 51 return pointerToID.GetAddress() 52 } 53 54 // CreateSampleTx creates a transaction with a single operation (BumpSequence), the min base fee, and infinite timebounds 55 func CreateSampleTx(sequence int64, operationCount int) xdr.TransactionEnvelope { 56 kp, err := keypair.Random() 57 PanicOnError(err) 58 59 operations := []txnbuild.Operation{} 60 operationType := &txnbuild.BumpSequence{ 61 BumpTo: 0, 62 } 63 for i := 0; i < operationCount; i++ { 64 operations = append(operations, operationType) 65 } 66 67 sourceAccount := txnbuild.NewSimpleAccount(kp.Address(), int64(0)) 68 tx, err := txnbuild.NewTransaction( 69 txnbuild.TransactionParams{ 70 SourceAccount: &sourceAccount, 71 Operations: operations, 72 BaseFee: txnbuild.MinBaseFee, 73 Preconditions: txnbuild.Preconditions{TimeBounds: txnbuild.NewInfiniteTimeout()}, 74 }, 75 ) 76 PanicOnError(err) 77 78 env := tx.ToXDR() 79 return env 80 } 81 82 // ConvertStroopValueToReal converts a value in stroops, the smallest amount unit, into real units 83 func ConvertStroopValueToReal(input xdr.Int64) float64 { 84 output, _ := big.NewRat(int64(input), int64(10000000)).Float64() 85 return output 86 } 87 88 // CreateSampleResultMeta creates Transaction results with the desired success flag and number of sub operation results 89 func CreateSampleResultMeta(successful bool, subOperationCount int) xdr.TransactionResultMeta { 90 resultCode := xdr.TransactionResultCodeTxFailed 91 if successful { 92 resultCode = xdr.TransactionResultCodeTxSuccess 93 } 94 operationResults := []xdr.OperationResult{} 95 operationResultTr := &xdr.OperationResultTr{ 96 Type: xdr.OperationTypeCreateAccount, 97 CreateAccountResult: &xdr.CreateAccountResult{ 98 Code: 0, 99 }, 100 } 101 102 for i := 0; i < subOperationCount; i++ { 103 operationResults = append(operationResults, xdr.OperationResult{ 104 Code: xdr.OperationResultCodeOpInner, 105 Tr: operationResultTr, 106 }) 107 } 108 109 return xdr.TransactionResultMeta{ 110 Result: xdr.TransactionResultPair{ 111 Result: xdr.TransactionResult{ 112 Result: xdr.TransactionResultResult{ 113 Code: resultCode, 114 Results: &operationResults, 115 }, 116 }, 117 }, 118 } 119 } 120 121 func CreateSampleResultPair(successful bool, subOperationCount int) xdr.TransactionResultPair { 122 resultCode := xdr.TransactionResultCodeTxFailed 123 if successful { 124 resultCode = xdr.TransactionResultCodeTxSuccess 125 } 126 operationResults := []xdr.OperationResult{} 127 operationResultTr := &xdr.OperationResultTr{ 128 Type: xdr.OperationTypeCreateAccount, 129 CreateAccountResult: &xdr.CreateAccountResult{ 130 Code: 0, 131 }, 132 } 133 134 for i := 0; i < subOperationCount; i++ { 135 operationResults = append(operationResults, xdr.OperationResult{ 136 Code: xdr.OperationResultCodeOpInner, 137 Tr: operationResultTr, 138 }) 139 } 140 141 return xdr.TransactionResultPair{ 142 Result: xdr.TransactionResult{ 143 Result: xdr.TransactionResultResult{ 144 Code: resultCode, 145 Results: &operationResults, 146 }, 147 }, 148 } 149 } 150 151 func CreateSampleTxMeta(subOperationCount int, AssetA, AssetB xdr.Asset) *xdr.TransactionMetaV1 { 152 operationMeta := []xdr.OperationMeta{} 153 for i := 0; i < subOperationCount; i++ { 154 operationMeta = append(operationMeta, xdr.OperationMeta{ 155 Changes: xdr.LedgerEntryChanges{}, 156 }) 157 } 158 159 operationMeta = AddLPOperations(operationMeta, AssetA, AssetB) 160 operationMeta = AddLPOperations(operationMeta, AssetA, AssetB) 161 162 operationMeta = append(operationMeta, xdr.OperationMeta{ 163 Changes: xdr.LedgerEntryChanges{}, 164 }) 165 166 return &xdr.TransactionMetaV1{ 167 Operations: operationMeta, 168 } 169 } 170 171 func AddLPOperations(txMeta []xdr.OperationMeta, AssetA, AssetB xdr.Asset) []xdr.OperationMeta { 172 txMeta = append(txMeta, xdr.OperationMeta{ 173 Changes: xdr.LedgerEntryChanges{ 174 xdr.LedgerEntryChange{ 175 Type: xdr.LedgerEntryChangeTypeLedgerEntryState, 176 State: &xdr.LedgerEntry{ 177 Data: xdr.LedgerEntryData{ 178 Type: xdr.LedgerEntryTypeLiquidityPool, 179 LiquidityPool: &xdr.LiquidityPoolEntry{ 180 LiquidityPoolId: xdr.PoolId{1, 2, 3, 4, 5, 6, 7, 8, 9}, 181 Body: xdr.LiquidityPoolEntryBody{ 182 Type: xdr.LiquidityPoolTypeLiquidityPoolConstantProduct, 183 ConstantProduct: &xdr.LiquidityPoolEntryConstantProduct{ 184 Params: xdr.LiquidityPoolConstantProductParameters{ 185 AssetA: AssetA, 186 AssetB: AssetB, 187 Fee: 30, 188 }, 189 ReserveA: 100000, 190 ReserveB: 1000, 191 TotalPoolShares: 500, 192 PoolSharesTrustLineCount: 25, 193 }, 194 }, 195 }, 196 }, 197 }, 198 }, 199 xdr.LedgerEntryChange{ 200 Type: xdr.LedgerEntryChangeTypeLedgerEntryUpdated, 201 Updated: &xdr.LedgerEntry{ 202 Data: xdr.LedgerEntryData{ 203 Type: xdr.LedgerEntryTypeLiquidityPool, 204 LiquidityPool: &xdr.LiquidityPoolEntry{ 205 LiquidityPoolId: xdr.PoolId{1, 2, 3, 4, 5, 6, 7, 8, 9}, 206 Body: xdr.LiquidityPoolEntryBody{ 207 Type: xdr.LiquidityPoolTypeLiquidityPoolConstantProduct, 208 ConstantProduct: &xdr.LiquidityPoolEntryConstantProduct{ 209 Params: xdr.LiquidityPoolConstantProductParameters{ 210 AssetA: AssetA, 211 AssetB: AssetB, 212 Fee: 30, 213 }, 214 ReserveA: 101000, 215 ReserveB: 1100, 216 TotalPoolShares: 502, 217 PoolSharesTrustLineCount: 26, 218 }, 219 }, 220 }, 221 }, 222 }, 223 }, 224 }}) 225 226 return txMeta 227 } 228 229 // AddCommonFlags adds the flags common to all commands: end-ledger, stdout, and strict-export 230 func AddCommonFlags(flags *pflag.FlagSet) { 231 flags.Uint32P("end-ledger", "e", 0, "The ledger sequence number for the end of the export range") 232 flags.Bool("strict-export", false, "If set, transform errors will be fatal.") 233 flags.Bool("testnet", false, "If set, will connect to Testnet instead of Mainnet.") 234 flags.Bool("futurenet", false, "If set, will connect to Futurenet instead of Mainnet.") 235 flags.StringToStringP("extra-fields", "u", map[string]string{}, "Additional fields to append to output jsons. Used for appending metadata") 236 } 237 238 // AddArchiveFlags adds the history archive specific flags: start-ledger, output, and limit 239 func AddArchiveFlags(objectName string, flags *pflag.FlagSet) { 240 flags.Uint32P("start-ledger", "s", 2, "The ledger sequence number for the beginning of the export period. Defaults to genesis ledger") 241 flags.StringP("output", "o", "exported_"+objectName+".txt", "Filename of the output file") 242 flags.Int64P("limit", "l", -1, "Maximum number of "+objectName+" to export. If the limit is set to a negative number, all the objects in the provided range are exported") 243 } 244 245 // AddBucketFlags adds the bucket list specifc flags: output 246 func AddBucketFlags(objectName string, flags *pflag.FlagSet) { 247 flags.StringP("output", "o", "exported_"+objectName+".txt", "Filename of the output file") 248 } 249 250 // AddCloudStorageFlags adds the cloud storage releated flags: cloud-storage-bucket, cloud-credentials 251 func AddCloudStorageFlags(flags *pflag.FlagSet) { 252 flags.String("cloud-storage-bucket", "stellar-etl-cli", "Cloud storage bucket to export to.") 253 flags.String("cloud-credentials", "", "Path to cloud provider service account credentials. Only used for local/dev purposes. "+ 254 "When run on GCP, credentials should be inferred by service account json.") 255 flags.String("cloud-provider", "", "Cloud provider for storage services.") 256 } 257 258 // AddCoreFlags adds the captive core specific flags: core-executable, core-config, batch-size, and output flags 259 func AddCoreFlags(flags *pflag.FlagSet, defaultFolder string) { 260 flags.StringP("core-executable", "x", "", "Filepath to the stellar-core executable") 261 flags.StringP("core-config", "c", "", "Filepath to the config file for stellar-core") 262 263 flags.Uint32P("batch-size", "b", 64, "number of ledgers to export changes from in each batches") 264 flags.StringP("output", "o", defaultFolder, "Folder that will contain the output files") 265 266 flags.Uint32P("start-ledger", "s", 2, "The ledger sequence number for the beginning of the export period. Defaults to genesis ledger") 267 } 268 269 // AddExportTypeFlags adds the captive core specifc flags: export-{type} flags 270 func AddExportTypeFlags(flags *pflag.FlagSet) { 271 flags.BoolP("export-accounts", "a", false, "set in order to export account changes") 272 flags.BoolP("export-trustlines", "t", false, "set in order to export trustline changes") 273 flags.BoolP("export-offers", "f", false, "set in order to export offer changes") 274 flags.BoolP("export-pools", "p", false, "set in order to export liquidity pool changes") 275 flags.BoolP("export-balances", "l", false, "set in order to export claimable balance changes") 276 flags.BoolP("export-contract-code", "", false, "set in order to export contract code changes") 277 flags.BoolP("export-contract-data", "", false, "set in order to export contract data changes") 278 flags.BoolP("export-config-settings", "", false, "set in order to export config settings changes") 279 flags.BoolP("export-ttl", "", false, "set in order to export ttl changes") 280 } 281 282 // MustCommonFlags gets the values of the the flags common to all commands: end-ledger and strict-export. If any do not exist, it stops the program fatally using the logger 283 func MustCommonFlags(flags *pflag.FlagSet, logger *EtlLogger) (endNum uint32, strictExport, isTest bool, isFuture bool, extra map[string]string) { 284 endNum, err := flags.GetUint32("end-ledger") 285 if err != nil { 286 logger.Fatal("could not get end sequence number: ", err) 287 } 288 289 strictExport, err = flags.GetBool("strict-export") 290 if err != nil { 291 logger.Fatal("could not get strict-export boolean: ", err) 292 } 293 294 isTest, err = flags.GetBool("testnet") 295 if err != nil { 296 logger.Fatal("could not get testnet boolean: ", err) 297 } 298 299 isFuture, err = flags.GetBool("futurenet") 300 if err != nil { 301 logger.Fatal("could not get futurenet boolean: ", err) 302 } 303 304 extra, err = flags.GetStringToString("extra-fields") 305 if err != nil { 306 logger.Fatal("could not get extra fields string: ", err) 307 } 308 return 309 } 310 311 // MustArchiveFlags gets the values of the the history archive specific flags: start-ledger, output, and limit 312 func MustArchiveFlags(flags *pflag.FlagSet, logger *EtlLogger) (startNum uint32, path string, limit int64) { 313 startNum, err := flags.GetUint32("start-ledger") 314 if err != nil { 315 logger.Fatal("could not get start sequence number: ", err) 316 } 317 318 path, err = flags.GetString("output") 319 if err != nil { 320 logger.Fatal("could not get output filename: ", err) 321 } 322 323 limit, err = flags.GetInt64("limit") 324 if err != nil { 325 logger.Fatal("could not get limit: ", err) 326 } 327 328 return 329 } 330 331 // MustBucketFlags gets the values of the bucket list specific flags: output 332 func MustBucketFlags(flags *pflag.FlagSet, logger *EtlLogger) (path string) { 333 path, err := flags.GetString("output") 334 if err != nil { 335 logger.Fatal("could not get output filename: ", err) 336 } 337 338 return 339 } 340 341 // MustCloudStorageFlags gets the values of the bucket list specific flags: cloud-storage-bucket, cloud-credentials 342 func MustCloudStorageFlags(flags *pflag.FlagSet, logger *EtlLogger) (bucket, credentials, provider string) { 343 bucket, err := flags.GetString("cloud-storage-bucket") 344 if err != nil { 345 logger.Fatal("could not get cloud storage bucket: ", err) 346 } 347 348 credentials, err = flags.GetString("cloud-credentials") 349 if err != nil { 350 logger.Fatal("could not get cloud credentials file: ", err) 351 } 352 353 provider, err = flags.GetString("cloud-provider") 354 if err != nil { 355 logger.Fatal("could not get cloud provider: ", err) 356 } 357 358 return 359 } 360 361 // MustCoreFlags gets the values for the core-executable, core-config, start ledger batch-size, and output flags. If any do not exist, it stops the program fatally using the logger 362 func MustCoreFlags(flags *pflag.FlagSet, logger *EtlLogger) (execPath, configPath string, startNum, batchSize uint32, path string) { 363 execPath, err := flags.GetString("core-executable") 364 if err != nil { 365 logger.Fatal("could not get path to stellar-core executable, which is mandatory when not starting at the genesis ledger (ledger 1): ", err) 366 } 367 368 configPath, err = flags.GetString("core-config") 369 if err != nil { 370 logger.Fatal("could not get path to stellar-core config file, is mandatory when not starting at the genesis ledger (ledger 1): ", err) 371 } 372 373 path, err = flags.GetString("output") 374 if err != nil { 375 logger.Fatal("could not get output filename: ", err) 376 } 377 378 startNum, err = flags.GetUint32("start-ledger") 379 if err != nil { 380 logger.Fatal("could not get start sequence number: ", err) 381 } 382 383 batchSize, err = flags.GetUint32("batch-size") 384 if err != nil { 385 logger.Fatal("could not get batch size: ", err) 386 } 387 388 return 389 } 390 391 // MustExportTypeFlags gets the values for the export-accounts, export-offers, and export-trustlines flags. If any do not exist, it stops the program fatally using the logger 392 // func MustExportTypeFlags(flags *pflag.FlagSet, logger *EtlLogger) (exportAccounts, exportOffers, exportTrustlines, exportPools, exportBalances, exportContractCode, exportContractData, exportConfigSettings, exportTtl bool) { 393 func MustExportTypeFlags(flags *pflag.FlagSet, logger *EtlLogger) map[string]bool { 394 var err error 395 exports := map[string]bool{ 396 "export-accounts": false, 397 "export-trustlines": false, 398 "export-offers": false, 399 "export-pools": false, 400 "export-balances": false, 401 "export-contract-code": false, 402 "export-contract-data": false, 403 "export-config-settings": false, 404 "export-ttl": false, 405 } 406 407 for export_name, _ := range exports { 408 exports[export_name], err = flags.GetBool(export_name) 409 if err != nil { 410 logger.Fatalf("could not get %s flag: %v", export_name, err) 411 } 412 } 413 414 return exports 415 } 416 417 type historyArchiveBackend struct { 418 client historyarchive.ArchiveInterface 419 ledgers map[uint32]*historyarchive.Ledger 420 } 421 422 func (h historyArchiveBackend) GetLatestLedgerSequence(ctx context.Context) (sequence uint32, err error) { 423 root, err := h.client.GetRootHAS() 424 if err != nil { 425 return 0, err 426 } 427 return root.CurrentLedger, nil 428 } 429 430 func (h historyArchiveBackend) GetLedgers(ctx context.Context) (map[uint32]*historyarchive.Ledger, error) { 431 432 return h.ledgers, nil 433 } 434 435 func (h historyArchiveBackend) GetLedgerArchive(ctx context.Context, sequence uint32) (historyarchive.Ledger, error) { 436 ledger, ok := h.ledgers[sequence] 437 if !ok { 438 return historyarchive.Ledger{}, fmt.Errorf("ledger %d is missing from map", sequence) 439 } 440 441 historyLedger := historyarchive.Ledger{ 442 Header: ledger.Header, 443 Transaction: ledger.Transaction, 444 TransactionResult: ledger.TransactionResult, 445 } 446 447 return historyLedger, nil 448 } 449 450 func (h historyArchiveBackend) GetLedger(ctx context.Context, sequence uint32) (xdr.LedgerCloseMeta, error) { 451 ledger, ok := h.ledgers[sequence] 452 if !ok { 453 return xdr.LedgerCloseMeta{}, fmt.Errorf("ledger %d is missing from map", sequence) 454 } 455 456 lcm := xdr.LedgerCloseMeta{ 457 V: 0, 458 V0: &xdr.LedgerCloseMetaV0{ 459 LedgerHeader: ledger.Header, 460 TxSet: ledger.Transaction.TxSet, 461 }, 462 } 463 lcm.V0.TxProcessing = make([]xdr.TransactionResultMeta, len(ledger.TransactionResult.TxResultSet.Results)) 464 for i, result := range ledger.TransactionResult.TxResultSet.Results { 465 lcm.V0.TxProcessing[i].Result = result 466 } 467 468 return lcm, nil 469 } 470 471 func (h historyArchiveBackend) PrepareRange(ctx context.Context, ledgerRange ledgerbackend.Range) error { 472 return nil 473 } 474 475 func (h historyArchiveBackend) IsPrepared(ctx context.Context, ledgerRange ledgerbackend.Range) (bool, error) { 476 return true, nil 477 } 478 479 func (h historyArchiveBackend) Close() error { 480 return nil 481 } 482 483 // ValidateLedgerRange validates the given ledger range 484 func ValidateLedgerRange(start, end, latestNum uint32) error { 485 if start == 0 { 486 return fmt.Errorf("start sequence number equal to 0. There is no ledger 0 (genesis ledger is ledger 1)") 487 } 488 489 if end == 0 { 490 return fmt.Errorf("end sequence number equal to 0. There is no ledger 0 (genesis ledger is ledger 1)") 491 } 492 493 if end < start { 494 return fmt.Errorf("end sequence number is less than start (%d < %d)", end, start) 495 } 496 497 if latestNum < start { 498 return fmt.Errorf("latest sequence number is less than start sequence number (%d < %d)", latestNum, start) 499 } 500 501 if latestNum < end { 502 return fmt.Errorf("latest sequence number is less than end sequence number (%d < %d)", latestNum, end) 503 } 504 505 return nil 506 } 507 508 func CreateBackend(start, end uint32, archiveURLs []string) (historyArchiveBackend, error) { 509 client, err := CreateHistoryArchiveClient(archiveURLs) 510 if err != nil { 511 return historyArchiveBackend{}, err 512 } 513 514 root, err := client.GetRootHAS() 515 if err != nil { 516 return historyArchiveBackend{}, err 517 } 518 if err = ValidateLedgerRange(start, end, root.CurrentLedger); err != nil { 519 return historyArchiveBackend{}, err 520 } 521 522 ledgers, err := client.GetLedgers(start, end) 523 if err != nil { 524 return historyArchiveBackend{}, err 525 } 526 return historyArchiveBackend{client: client, ledgers: ledgers}, nil 527 } 528 529 // mainnet history archive URLs 530 var mainArchiveURLs = []string{ 531 "https://history.stellar.org/prd/core-live/core_live_001", 532 "https://history.stellar.org/prd/core-live/core_live_002", 533 "https://history.stellar.org/prd/core-live/core_live_003", 534 } 535 536 // testnet is only used for local testing with new Protocol features 537 var testArchiveURLs = []string{ 538 "https://history.stellar.org/prd/core-testnet/core_testnet_001", 539 "https://history.stellar.org/prd/core-testnet/core_testnet_002", 540 "https://history.stellar.org/prd/core-testnet/core_testnet_003", 541 } 542 543 // futrenet is used for testing new Protocol features 544 var futureArchiveURLs = []string{ 545 "https://history-futurenet.stellar.org/", 546 } 547 548 func CreateHistoryArchiveClient(archiveURLS []string) (historyarchive.ArchiveInterface, error) { 549 archiveOptions := historyarchive.ArchiveOptions{ 550 ConnectOptions: storage.ConnectOptions{ 551 UserAgent: "stellar-etl/1.0.0", 552 }, 553 } 554 return historyarchive.NewArchivePool(archiveURLS, archiveOptions) 555 } 556 557 // GetLatestLedgerSequence returns the latest ledger sequence 558 func GetLatestLedgerSequence(archiveURLs []string) (uint32, error) { 559 client, err := CreateHistoryArchiveClient(archiveURLs) 560 if err != nil { 561 return 0, err 562 } 563 564 root, err := client.GetRootHAS() 565 if err != nil { 566 return 0, err 567 } 568 569 return root.CurrentLedger, nil 570 } 571 572 // GetCheckpointNum gets the ledger sequence number of the checkpoint containing the provided ledger. If the checkpoint does not exist, an error is returned 573 func GetCheckpointNum(seq, maxSeq uint32) (uint32, error) { 574 /* 575 Checkpoints are made "every 64 ledgers", when LCL is one-less-than a multiple 576 of 64. In other words, at LCL=63, 127, 191, 255, etc. or in other other words 577 checkpoint K covers the inclusive ledger range [K*64, ((K+1)*64)-1], and each 578 of those ranges should contain exactly 64 ledgers, with the exception of the 579 first checkpoint, which has only 63 ledgers: there is no ledger 0. 580 */ 581 remainder := (seq + 1) % 64 582 if remainder == 0 { 583 return seq, nil 584 } 585 586 checkpoint := seq + 64 - remainder 587 if checkpoint > maxSeq { 588 return 0, fmt.Errorf("the checkpoint ledger %d is greater than the max ledger number %d", checkpoint, maxSeq) 589 } 590 591 return checkpoint, nil 592 } 593 594 // ExtractLedgerCloseTime gets the close time of the provided ledger 595 func ExtractLedgerCloseTime(ledger xdr.LedgerHeaderHistoryEntry) (time.Time, error) { 596 return TimePointToUTCTimeStamp(ledger.Header.ScpValue.CloseTime) 597 } 598 599 // ExtractEntryFromChange gets the most recent state of an entry from an ingestio change, as well as if the entry was deleted 600 func ExtractEntryFromChange(change ingest.Change) (xdr.LedgerEntry, xdr.LedgerEntryChangeType, bool, error) { 601 switch changeType := change.LedgerEntryChangeType(); changeType { 602 case xdr.LedgerEntryChangeTypeLedgerEntryCreated, xdr.LedgerEntryChangeTypeLedgerEntryUpdated: 603 return *change.Post, changeType, false, nil 604 case xdr.LedgerEntryChangeTypeLedgerEntryRemoved: 605 return *change.Pre, changeType, true, nil 606 default: 607 return xdr.LedgerEntry{}, changeType, false, fmt.Errorf("unable to extract ledger entry type from change") 608 } 609 } 610 611 // GetMostRecentCheckpoint returns the most recent checkpoint before the provided ledger 612 func GetMostRecentCheckpoint(seq uint32) uint32 { 613 remainder := (seq + 1) % 64 614 if remainder == 0 { 615 return seq 616 } 617 return seq - remainder 618 } 619 620 type EnvironmentDetails struct { 621 NetworkPassphrase string 622 ArchiveURLs []string 623 BinaryPath string 624 CoreConfig string 625 } 626 627 // GetPassphrase returns the correct Network Passphrase based on env preference 628 func GetEnvironmentDetails(isTest bool, isFuture bool) (details EnvironmentDetails) { 629 if isTest { 630 // testnet passphrase to be used for testing 631 details.NetworkPassphrase = network.TestNetworkPassphrase 632 details.ArchiveURLs = testArchiveURLs 633 details.BinaryPath = "/usr/bin/stellar-core" 634 details.CoreConfig = "docker/stellar-core_testnet.cfg" 635 return details 636 } else if isFuture { 637 // details.NetworkPassphrase = network.FutureNetworkPassphrase 638 details.NetworkPassphrase = "Test SDF Future Network ; October 2022" 639 details.ArchiveURLs = futureArchiveURLs 640 details.BinaryPath = "/usr/bin/stellar-core" 641 details.CoreConfig = "docker/stellar-core_futurenet.cfg" 642 return details 643 } else { 644 // default: mainnet 645 details.NetworkPassphrase = network.PublicNetworkPassphrase 646 details.ArchiveURLs = mainArchiveURLs 647 details.BinaryPath = "/usr/bin/stellar-core" 648 details.CoreConfig = "docker/stellar-core.cfg" 649 return details 650 } 651 } 652 653 type CaptiveCore interface { 654 CreateCaptiveCoreBackend() (ledgerbackend.CaptiveStellarCore, error) 655 } 656 657 func (e EnvironmentDetails) CreateCaptiveCoreBackend() (*ledgerbackend.CaptiveStellarCore, error) { 658 captiveCoreToml, err := ledgerbackend.NewCaptiveCoreTomlFromFile( 659 e.CoreConfig, 660 ledgerbackend.CaptiveCoreTomlParams{ 661 NetworkPassphrase: e.NetworkPassphrase, 662 HistoryArchiveURLs: e.ArchiveURLs, 663 Strict: true, 664 UseDB: false, 665 }, 666 ) 667 if err != nil { 668 return &ledgerbackend.CaptiveStellarCore{}, err 669 } 670 backend, err := ledgerbackend.NewCaptive( 671 ledgerbackend.CaptiveCoreConfig{ 672 BinaryPath: e.BinaryPath, 673 Toml: captiveCoreToml, 674 NetworkPassphrase: e.NetworkPassphrase, 675 HistoryArchiveURLs: e.ArchiveURLs, 676 UseDB: false, 677 UserAgent: "stellar-etl/1.0.0", 678 }, 679 ) 680 return backend, err 681 } 682 683 func (e EnvironmentDetails) GetUnboundedLedgerCloseMeta(end uint32) (xdr.LedgerCloseMeta, error) { 684 ctx := context.Background() 685 686 backend, err := e.CreateCaptiveCoreBackend() 687 688 ledgerRange := ledgerbackend.UnboundedRange(end) 689 690 err = backend.PrepareRange(ctx, ledgerRange) 691 if err != nil { 692 return xdr.LedgerCloseMeta{}, err 693 } 694 695 ledgerCloseMeta, err := backend.GetLedger(ctx, end) 696 if err != nil { 697 return xdr.LedgerCloseMeta{}, err 698 } 699 700 return ledgerCloseMeta, nil 701 } 702 703 func GetCloseTime(lcm xdr.LedgerCloseMeta) (time.Time, error) { 704 headerHistoryEntry := lcm.LedgerHeaderHistoryEntry() 705 return ExtractLedgerCloseTime(headerHistoryEntry) 706 } 707 708 func LedgerEntryToLedgerKeyHash(ledgerEntry xdr.LedgerEntry) string { 709 ledgerKey, _ := ledgerEntry.LedgerKey() 710 ledgerKeyByte, _ := ledgerKey.MarshalBinary() 711 hashedLedgerKeyByte := hash.Hash(ledgerKeyByte) 712 ledgerKeyHash := hex.EncodeToString(hashedLedgerKeyByte[:]) 713 714 return ledgerKeyHash 715 }