Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Fixed

- A rollback whose target is at or beyond the store's own version no longer moves ledger chunk metadata forward past it, which previously left a permanent offset skewing later chunk boundaries (#8244).
- Ledger chunk metadata and snapshot scheduling are no longer restored by a transaction whose writes a concurrent view change has already discarded. Both are now updated under the same lock as the rollback, and skipped when the transaction's rollback epoch or view no longer holds (#8243).
- A transaction whose view changed while it was committing could apply its writes to the local key-value store and then fail to replicate, leaving state that never reached consensus. The transaction's view is now validated atomically with the allocation of its version, so it is rejected before any map is modified, and `ccf::kv::CommitResult::FAIL_NO_REPLICATE` no longer implies a locally applied write (#8242).

Expand Down
7 changes: 4 additions & 3 deletions src/kv/store.h
Original file line number Diff line number Diff line change
Expand Up @@ -692,9 +692,10 @@ namespace ccf::kv
}
if (chunker)
{
// Keep this ordered with append_entry_size() below, so a commit
// cannot restore chunk metadata after this rollback.
chunker->rolled_back_to(tx_id.seqno);
// Nothing local is discarded here, but the rollback target may be
// at or beyond the Store's current version. Clamp so this cannot
// move chunk metadata forward past the Store.
chunker->rolled_back_to(std::min<Version>(tx_id.seqno, version));
}
return;
}
Expand Down
45 changes: 45 additions & 0 deletions src/kv/test/kv_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3596,6 +3596,51 @@ TEST_CASE("Chunk metadata is not restored by a batch a rollback discarded")
CHECK(chunker->current_version() == store.current_version());
}

TEST_CASE("A rollback never moves chunk metadata past the store's version")
{
ccf::kv::Store store;
store.set_encryptor(std::make_shared<ccf::kv::NullTxEncryptor>());
auto consensus = std::make_shared<ccf::kv::test::PrimaryStubConsensus>();
store.set_consensus(consensus);
auto chunker = std::make_shared<InspectableChunker>();
store.set_chunker(chunker);

constexpr ccf::kv::Term initial_term = 2;
store.initialise_term(initial_term);
MapTypes::StringString map("public:map");

{
auto tx = store.create_tx();
tx.rw(map)->put("key", "initial");
REQUIRE(tx.commit() == ccf::kv::CommitResult::SUCCESS);
}

const auto version = store.current_version();
REQUIRE(chunker->current_version() == version);

SUBCASE("Rollback to the current version")
{
store.rollback(store.current_txid(), initial_term + 1);
}

SUBCASE("Rollback beyond the current version")
{
store.rollback({initial_term, version + 3}, initial_term + 1);
}

// Neither discards anything, so neither may move the chunker.
CHECK(store.current_version() == version);
CHECK(chunker->current_version() == version);

INFO("Later entries are still recorded against their own version");
{
auto tx = store.create_tx();
tx.rw(map)->put("key", "fresh");
REQUIRE(tx.commit() == ccf::kv::CommitResult::SUCCESS);
}
CHECK(chunker->current_version() == store.current_version());
}

TEST_CASE("Ledger entry chunk request")
{
ccf::kv::Store store;
Expand Down