Skip to content

0.4.0

Choose a tag to compare

@AlexanderBuzz AlexanderBuzz released this 07 Sep 11:19
· 50 commits to master since this release
ff832e7

The last of the three WooCommerce retrofit findings, and the only one that needs a schema migration in every adapter — which is why it was split out of 0.3.0 rather than shipped with it.

Breaking for adapters

Change What to do
getLastSyncedLedgerIndex($destinationAccount, $network) Add a network column (VARCHAR(16) NOT NULL) to ledger_direct_xrpl_tx and an index on (destination, network, ledger_index); filter on both. Backfill existing rows or treat them as unknown.
XrplTransaction Gains a leading $network constructor argument

WooCommerce is live, so this goes through LedgerDirectInstall::maybe_upgrade() with DB_VERSION 3.

Why the cursor had to be scoped

getLastSyncedLedgerIndex() was a global MAX(ledger_index) over the whole transaction table, ported straight from Shopware, where one shop meant one account and one network. Two failures follow from that:

  • A testnet reset stranded the cursor permanently. The testnet is reset periodically, which rewinds its ledger index. The dev database held rows from the previous epoch at ~45M while the live testnet was back at ~20.5M, so the cursor asked for a ledger range that does not exist and every sync failed with lgrIdxsInvalid — for every order, indefinitely, until the rows were deleted by hand.
  • One mainnet row poisoned testnet forever. A mainnet row (index ~100M) pins the cursor above every testnet ledger, making a mainnet→testnet switch impossible without a database edit. The same applies across accounts: a merchant who changes receiving account resumed at the old account's cursor and never saw anything before it.

A ledger index only means anything within one network, so the cursor has to be scoped by network and account. The network is stored rather than derived: the CTID does encode a network id, but parsing it back out is far more work than a column.

Surviving a reset without touching the database

On lgrIdxsInvalid, and only when a cursor was actually sent, SyncService logs a PSR-3 warning and repeats the sync once with no ledger_index_min at all. findExistingHashes() keeps that from duplicating anything. Without a cursor the same error means something else entirely, so it propagates rather than doubling every failing sync — a guard that is not in the handover document and has its own test.

XrplRpcException now carries rippled's own error code, so that branch keys off a code rather than matching message text.

Rows that survive a reset are worthless but harmless: they are not deleted, just out-ranked by the resync.

Notes

123 tests, green on PHP 8.2 / 8.3 / 8.4 plus the lowest-dependency job. Contract details in INVARIANTS.md under Tables and "The testnet is not persistent".