Repository navigation
0.4.0
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".