14. 퀘스트 완료 보상 수령 (중복 수령 방지 / 멱등성)
난이도 하 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 퀘스트 완료 보상 수령 (중복 수령 방지 / 멱등성)
// ----------------------------------------------------------------------------
// 시나리오:
// - 플레이어가 퀘스트를 완료하면 상태가 Completed 가 되고, "보상 받기" 버튼을
// 누르면 클라이언트가 C_ClaimQuestReward(questId) 패킷을 보낸다.
// - 서버는 보상(골드/아이템)을 지급하고 퀘스트 상태를 Claimed 로 바꾼다.
// - 보상은 단 한 번만 지급되어야 한다(같은 퀘스트로 두 번 받으면 안 됨).
// - 클라이언트는 신뢰할 수 없고, 버튼 더블클릭/매크로/네트워크 재전송으로
// 같은 C_ClaimQuestReward 패킷이 거의 동시에 두 번 이상 도착할 수 있다.
// - 같은 플레이어의 패킷이라도 서로 다른 IO 스레드에서 동시에 처리될 수 있다.
//
// 요구사항:
// - 보상 지급 여부는 서버가 권위 있게 판정한다.
// - 어떤 동시성/재전송 상황에서도 보상은 정확히 한 번만 지급되어야 한다.
// - 보상 지급(골드/아이템)과 상태 전이(Claimed)는 함께 일어나야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 악용되는지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
public enum QuestState { InProgress, Completed, Claimed }
public class QuestReward
{
public long Gold;
public int ItemId;
public int ItemCount;
}
public class Player
{
public long Id;
public long Gold;
// questId -> 상태
public Dictionary<int, QuestState> Quests = new Dictionary<int, QuestState>();
public Inventory Bag = new Inventory();
}
public class Inventory
{
public void Add(int itemId, int count) { /* 가방에 추가 (생략) */ }
}
public class QuestService
{
private readonly Dictionary<int, QuestReward> _rewards; // questId -> 보상 정의
private readonly Dictionary<long, Player> _players;
public QuestService(Dictionary<int, QuestReward> rewards, Dictionary<long, Player> players)
{
_rewards = rewards;
_players = players;
}
// C_ClaimQuestReward 처리. 지급 성공 시 true.
public bool ClaimReward(long playerId, int questId)
{
Player p = _players[playerId];
// (A) 수령 가능 여부 판정
if (p.Quests[questId] != QuestState.Completed)
return false;
QuestReward r = _rewards[questId];
// (B) 보상 지급
p.Gold += r.Gold;
p.Bag.Add(r.ItemId, r.ItemCount);
// (C) 상태 전이
p.Quests[questId] = QuestState.Claimed;
return true;
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 퀘스트 완료 보상 수령 (중복 수령 방지 / 멱등성)
// ----------------------------------------------------------------------------
// 시나리오:
// - 플레이어가 퀘스트를 완료하면 상태가 Completed 가 되고, "보상 받기" 버튼을
// 누르면 클라이언트가 C_ClaimQuestReward(questId) 패킷을 보낸다.
// - 서버는 보상(골드/아이템)을 지급하고 퀘스트 상태를 Claimed 로 바꾼다.
// - 보상은 단 한 번만 지급되어야 한다(같은 퀘스트로 두 번 받으면 안 됨).
// - 클라이언트는 신뢰할 수 없고, 버튼 더블클릭/매크로/네트워크 재전송으로
// 같은 C_ClaimQuestReward 패킷이 거의 동시에 두 번 이상 도착할 수 있다.
// - 같은 플레이어의 패킷이라도 서로 다른 IO 스레드에서 동시에 처리될 수 있다.
//
// 요구사항:
// - 보상 지급 여부는 서버가 권위 있게 판정한다.
// - 어떤 동시성/재전송 상황에서도 보상은 정확히 한 번만 지급되어야 한다.
// - 보상 지급(골드/아이템)과 상태 전이(Claimed)는 함께 일어나야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 악용되는지 설명하고,
// 수정안과 더 나은 설계를 제시하라. (C++ 고유 위험도 함께 다룰 것)
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <unordered_map>
enum class QuestState { InProgress, Completed, Claimed };
struct QuestReward {
int64_t gold = 0;
int itemId = 0;
int itemCount = 0;
};
struct Inventory {
void Add(int /*itemId*/, int /*count*/) { /* 가방에 추가 (생략) */ }
};
struct Player {
int64_t id = 0;
int64_t gold = 0;
std::unordered_map<int, QuestState> quests; // questId -> 상태
Inventory bag;
};
class QuestService {
public:
QuestService(std::unordered_map<int, QuestReward>& rewards,
std::unordered_map<int64_t, Player>& players)
: rewards_(rewards), players_(players) {}
// C_ClaimQuestReward 처리. 지급 성공 시 true.
bool ClaimReward(int64_t playerId, int questId)
{
Player& p = players_[playerId];
// (A) 수령 가능 여부 판정
if (p.quests[questId] != QuestState::Completed)
return false;
QuestReward& r = rewards_[questId];
// (B) 보상 지급
p.gold += r.gold;
p.bag.Add(r.itemId, r.itemCount);
// (C) 상태 전이
p.quests[questId] = QuestState::Claimed;
return true;
}
private:
std::unordered_map<int, QuestReward>& rewards_;
std::unordered_map<int64_t, Player>& players_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.