mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-15 23:52:06 +00:00
fix: bind rated result to durable receipt
This commit is contained in:
@@ -94,6 +94,9 @@ func CompleteResult(ctx context.Context, db *sql.DB, receipt domain.ResultReceip
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CompleteResultWithResult(ctx context.Context, db *sql.DB, receipt domain.ResultReceipt, serverID, eventID string, payload []byte, result domain.MatchResult, now time.Time) error {
|
func CompleteResultWithResult(ctx context.Context, db *sql.DB, receipt domain.ResultReceipt, serverID, eventID string, payload []byte, result domain.MatchResult, now time.Time) error {
|
||||||
|
if result.MatchID != receipt.MatchID || result.ServerID != serverID || result.ResultNonce != receipt.ResultNonce || result.IntegrityState != receipt.IntegrityState || domain.ResultDigest(result) != receipt.PayloadDigest {
|
||||||
|
return fmt.Errorf("result does not match receipt")
|
||||||
|
}
|
||||||
return completeResult(ctx, db, receipt, serverID, eventID, payload, now, &result)
|
return completeResult(ctx, db, receipt, serverID, eventID, payload, now, &result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
package store
|
package store
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cosmic-clash/cosmic-clash/server/domain"
|
||||||
|
)
|
||||||
|
|
||||||
func TestResultSQLPreservesReceiptConflictAndAtomicCommitBoundaries(t *testing.T) {
|
func TestResultSQLPreservesReceiptConflictAndAtomicCommitBoundaries(t *testing.T) {
|
||||||
checks := map[string][]string{
|
checks := map[string][]string{
|
||||||
ResultReceiptInsertSQL: {"ON CONFLICT DO NOTHING", "payload_digest", "integrity_state"},
|
ResultReceiptInsertSQL: {"ON CONFLICT DO NOTHING", "payload_digest", "integrity_state"},
|
||||||
ResultReceiptSelectSQL: {"FOR UPDATE", "committed_at"},
|
ResultReceiptSelectSQL: {"FOR UPDATE", "committed_at"},
|
||||||
ResultCommitLockSQL: {"server_id = $2", "FOR UPDATE"},
|
ResultCommitLockSQL: {"server_id = $2", "FOR UPDATE"},
|
||||||
ResultMatchCompleteSQL: {"state = 'RESULT_PENDING'", "revision = revision + 1"},
|
ResultMatchCompleteSQL: {"state = 'RESULT_PENDING'", "revision = revision + 1"},
|
||||||
ResultReceiptCommitSQL: {"COALESCE(committed_at", "committed_at"},
|
ResultReceiptCommitSQL: {"COALESCE(committed_at", "committed_at"},
|
||||||
ResultOutboxSQL: {"match_completed", "aggregate_id", "revision"},
|
ResultOutboxSQL: {"match_completed", "aggregate_id", "revision"},
|
||||||
RatingLockSQL: {"ORDER BY player_id", "FOR UPDATE"},
|
RatingLockSQL: {"ORDER BY player_id", "FOR UPDATE"},
|
||||||
MatchParticipantRatingsSQL: {"match_participants", "JOIN ratings", "ORDER BY mp.player_id"},
|
MatchParticipantRatingsSQL: {"match_participants", "JOIN ratings", "ORDER BY mp.player_id"},
|
||||||
RatingValuesSQL: {"player_id = ANY($1)", "ORDER BY player_id"},
|
RatingValuesSQL: {"player_id = ANY($1)", "ORDER BY player_id"},
|
||||||
RatingUpdateSQL: {"ranked_games = ranked_games + $5", "revision = revision + 1"},
|
RatingUpdateSQL: {"ranked_games = ranked_games + $5", "revision = revision + 1"},
|
||||||
@@ -24,6 +30,22 @@ func TestResultSQLPreservesReceiptConflictAndAtomicCommitBoundaries(t *testing.T
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCompleteResultWithResultRejectsReceiptResultMismatchBeforeDatabaseUse(t *testing.T) {
|
||||||
|
now := time.Unix(100, 0).UTC()
|
||||||
|
result := domain.MatchResult{
|
||||||
|
MatchID: "match", ServerID: "server", ResultNonce: "nonce-1234567890123456",
|
||||||
|
Team0Score: 1, Team1Score: 0, IntegrityState: domain.IntegrityCertified,
|
||||||
|
}
|
||||||
|
receipt := domain.ResultReceipt{
|
||||||
|
ResultID: "result", MatchID: result.MatchID, ResultNonce: result.ResultNonce,
|
||||||
|
PayloadDigest: domain.ResultDigest(result), IntegrityState: result.IntegrityState, ReceivedAt: now,
|
||||||
|
}
|
||||||
|
result.Team0Score = 2
|
||||||
|
if err := CompleteResultWithResult(context.Background(), nil, receipt, "server", "event", []byte("payload"), result, now); err == nil {
|
||||||
|
t.Fatal("mismatched result was accepted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func contains(value, fragment string) bool {
|
func contains(value, fragment string) bool {
|
||||||
for i := 0; i+len(fragment) <= len(value); i++ {
|
for i := 0; i+len(fragment) <= len(value); i++ {
|
||||||
if value[i:i+len(fragment)] == fragment {
|
if value[i:i+len(fragment)] == fragment {
|
||||||
|
|||||||
Reference in New Issue
Block a user