|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
| 3 | +import importlib.metadata |
3 | 4 | import json |
4 | 5 | import shutil |
5 | 6 | import socket |
@@ -498,3 +499,97 @@ def builder() -> TransactionBuilder: |
498 | 499 |
|
499 | 500 | # All four transactions, including the non-Void invocation, advanced the ledger. |
500 | 501 | assert _rpc(server.port(), 'getLatestLedger', {})['result']['sequence'] == 4 |
| 502 | + |
| 503 | + |
| 504 | +def test_get_version_info(server: StellarRpcServer) -> None: |
| 505 | + """getVersionInfo returns exactly the five spec fields with the right JSON types. |
| 506 | +
|
| 507 | + Real stellar-rpc (protocol 22+) emits camelCase keys only; the deprecated snake_case |
| 508 | + aliases (``commit_hash``, ...) were removed, so an exact key-set check covers both the |
| 509 | + required fields and the absence of the legacy ones. ``protocolVersion`` is a Go uint32, |
| 510 | + i.e. a JSON number, not a string. |
| 511 | + """ |
| 512 | + resp = _rpc(server.port(), 'getVersionInfo', {}) |
| 513 | + assert 'error' not in resp, resp |
| 514 | + result = resp['result'] |
| 515 | + |
| 516 | + assert set(result) == {'version', 'commitHash', 'buildTimestamp', 'captiveCoreVersion', 'protocolVersion'} |
| 517 | + # komet-node reports its own package version as the RPC server version. |
| 518 | + assert result['version'] == importlib.metadata.version('komet-node') |
| 519 | + assert type(result['commitHash']) is str |
| 520 | + assert type(result['buildTimestamp']) is str |
| 521 | + assert type(result['captiveCoreVersion']) is str |
| 522 | + assert type(result['protocolVersion']) is int # `is int` also rejects booleans |
| 523 | + assert result['protocolVersion'] == 22 |
| 524 | + |
| 525 | + |
| 526 | +def test_get_version_info_accepts_omitted_params(server: StellarRpcServer) -> None: |
| 527 | + """getVersionInfo takes no parameters; a request without a params member must succeed.""" |
| 528 | + resp = _post(server.port(), b'{"jsonrpc": "2.0", "id": 1, "method": "getVersionInfo"}') |
| 529 | + assert 'error' not in resp, resp |
| 530 | + assert resp['result']['protocolVersion'] == 22 |
| 531 | + |
| 532 | + |
| 533 | +# Every FeeDistribution field except ledgerCount is an unsigned integer serialised with Go's |
| 534 | +# `,string` option, i.e. a JSON string holding a decimal number (see the getFeeStats spec |
| 535 | +# example: `"transactionCount": "10"` but `"ledgerCount": 50`). |
| 536 | +_FEE_DISTRIBUTION_STRING_FIELDS = ( |
| 537 | + 'max', |
| 538 | + 'min', |
| 539 | + 'mode', |
| 540 | + 'p10', |
| 541 | + 'p20', |
| 542 | + 'p30', |
| 543 | + 'p40', |
| 544 | + 'p50', |
| 545 | + 'p60', |
| 546 | + 'p70', |
| 547 | + 'p80', |
| 548 | + 'p90', |
| 549 | + 'p95', |
| 550 | + 'p99', |
| 551 | + 'transactionCount', |
| 552 | +) |
| 553 | + |
| 554 | + |
| 555 | +def _assert_fee_distribution(dist: dict[str, Any]) -> None: |
| 556 | + """Check one FeeDistribution object against the stellar-rpc wire format.""" |
| 557 | + assert set(dist) == {*_FEE_DISTRIBUTION_STRING_FIELDS, 'ledgerCount'} |
| 558 | + for field in _FEE_DISTRIBUTION_STRING_FIELDS: |
| 559 | + value = dist[field] |
| 560 | + assert type(value) is str, f'{field} must be a JSON string, got {type(value).__name__}' |
| 561 | + assert value.isdigit(), f'{field} must hold a decimal number, got {value!r}' |
| 562 | + assert type(dist['ledgerCount']) is int, 'ledgerCount must be a JSON number' |
| 563 | + # The distribution must at least be internally consistent. |
| 564 | + assert int(dist['min']) <= int(dist['p50']) <= int(dist['max']) |
| 565 | + |
| 566 | + |
| 567 | +def test_get_fee_stats(server: StellarRpcServer) -> None: |
| 568 | + """getFeeStats returns both fee distributions and latestLedger with the right JSON types.""" |
| 569 | + resp = _rpc(server.port(), 'getFeeStats', {}) |
| 570 | + assert 'error' not in resp, resp |
| 571 | + result = resp['result'] |
| 572 | + |
| 573 | + assert set(result) == {'sorobanInclusionFee', 'inclusionFee', 'latestLedger'} |
| 574 | + _assert_fee_distribution(result['sorobanInclusionFee']) |
| 575 | + _assert_fee_distribution(result['inclusionFee']) |
| 576 | + assert type(result['latestLedger']) is int # a JSON number, and 0 on a fresh chain |
| 577 | + assert result['latestLedger'] == 0 |
| 578 | + |
| 579 | + |
| 580 | +def test_get_fee_stats_latest_ledger_tracks_chain(server: StellarRpcServer) -> None: |
| 581 | + """getFeeStats reports the live ledger sequence, not a constant.""" |
| 582 | + keypair = Keypair.random() |
| 583 | + account = Account(keypair.public_key, sequence=0) |
| 584 | + envelope = ( |
| 585 | + TransactionBuilder(account, Network.TESTNET_NETWORK_PASSPHRASE) |
| 586 | + .append_create_account_op(destination=keypair.public_key, starting_balance='1000') |
| 587 | + .set_timeout(30) |
| 588 | + .build() |
| 589 | + ) |
| 590 | + envelope.sign(keypair) |
| 591 | + assert _rpc(server.port(), 'sendTransaction', {'transaction': envelope.to_xdr()})['result']['status'] == 'PENDING' |
| 592 | + |
| 593 | + resp = _rpc(server.port(), 'getFeeStats', {}) |
| 594 | + assert 'error' not in resp, resp |
| 595 | + assert resp['result']['latestLedger'] == 1 |
0 commit comments