Commit 54031475 authored by zeroleak's avatar zeroleak
Browse files

rename round -> mix

parent 4d7fa2a2
......@@ -30,27 +30,27 @@ The node will be used to verify UTXO and broadcast tx.
### UTXO amounts
```
server.round.denomination: amount in satoshis
server.round.miner-fee: miner fee (only paid by mustMix)
server.mix.denomination: amount in satoshis
server.mix.miner-fee: miner fee (only paid by mustMix)
```
UTXO for mustMix should be founded with *server.round.denomination*<br/>
UTXO for liquidities should be founded with *server.round.denomination*+*server.round.miner-fee*
UTXO for mustMix should be founded with *server.mix.denomination*<br/>
UTXO for liquidities should be founded with *server.mix.denomination*+*server.mix.miner-fee*
### Round limits
### Mix limits
```
server.round.anonymity-set-target = 10
server.round.anonymity-set-min = 6
server.round.anonymity-set-max = 20
server.round.anonymity-set-adjust-timeout = 120
server.mix.anonymity-set-target = 10
server.mix.anonymity-set-min = 6
server.mix.anonymity-set-max = 20
server.mix.anonymity-set-adjust-timeout = 120
server.round.must-mix-min = 1
server.round.liquidity-timeout = 60
server.mix.must-mix-min = 1
server.mix.liquidity-timeout = 60
```
Round will start when *server.round.anonymity-set-target* (mustMix + liquidities) are registered.<br/>
If this target is not met after *server.round.anonymity-set-adjust-timeout*, it will be gradually decreased to *server.round.anonymity-set-min*.<br/>
Mix will start when *server.mix.anonymity-set-target* (mustMix + liquidities) are registered.<br/>
If this target is not met after *server.mix.anonymity-set-adjust-timeout*, it will be gradually decreased to *server.mix.anonymity-set-min*.<br/>
At the beginning of the round, only mustMix can register. Meanwhile, liquidities connecting are placed on a waiting pool.<br/>
After *server.round.liquidity-timeout* or when current *anonymity-set-target* is reached, liquidities are added as soon as *server.round.must-mix-min* is reached, up to *server.round.anonymity-set-max* inputs for the round.
At the beginning of the mix, only mustMix can register. Meanwhile, liquidities connecting are placed on a waiting pool.<br/>
After *server.mix.liquidity-timeout* or when current *anonymity-set-target* is reached, liquidities are added as soon as *server.mix.must-mix-min* is reached, up to *server.mix.anonymity-set-max* inputs for the mix.
### Testing
```
......
package com.samourai.whirlpool.server.beans;
import com.samourai.whirlpool.protocol.v1.notifications.RoundStatus;
import com.samourai.whirlpool.server.exceptions.RoundException;
import com.samourai.whirlpool.server.persistence.to.RoundTO;
import com.samourai.whirlpool.protocol.v1.notifications.MixStatus;
import com.samourai.whirlpool.server.exceptions.MixException;
import com.samourai.whirlpool.server.persistence.to.MixTO;
import com.samourai.whirlpool.server.utils.Utils;
import org.bitcoinj.core.Transaction;
import java.sql.Timestamp;
import java.util.*;
public class Round {
private RoundTO roundTO;
private String roundId;
public class Mix {
private MixTO mixTO;
private String mixId;
private Timestamp timeStarted;
private Map<RoundStatus,Timestamp> timeStatus;
private Map<MixStatus,Timestamp> timeStatus;
private long denomination; // in satoshis
private long fees; // in satoshis
private int minMustMix;
......@@ -25,7 +25,7 @@ public class Round {
private boolean acceptLiquidities;
private long liquidityTimeout; // wait X seconds for accepting liquidities
private RoundStatus roundStatus;
private MixStatus mixStatus;
private Map<String,RegisteredInput> inputsById;
private List<String> sendAddresses;
......@@ -37,9 +37,9 @@ public class Round {
private Transaction tx;
private FailReason failReason;
public Round(String roundId, long denomination, long fees, int minMustMix, int targetAnonymitySet, int minAnonymitySet, int maxAnonymitySet, long timeoutAdjustAnonymitySet, long liquidityTimeout) {
this.roundTO = null;
this.roundId = roundId;
public Mix(String mixId, long denomination, long fees, int minMustMix, int targetAnonymitySet, int minAnonymitySet, int maxAnonymitySet, long timeoutAdjustAnonymitySet, long liquidityTimeout) {
this.mixTO = null;
this.mixId = mixId;
this.timeStarted = new Timestamp(System.currentTimeMillis());
this.timeStatus = new HashMap<>();
this.denomination = denomination;
......@@ -53,7 +53,7 @@ public class Round {
this.acceptLiquidities = false;
this.liquidityTimeout = liquidityTimeout;
this.roundStatus = RoundStatus.REGISTER_INPUT;
this.mixStatus = MixStatus.REGISTER_INPUT;
this.inputsById = new HashMap<>();
this.sendAddresses = new LinkedList<>();
......@@ -66,31 +66,31 @@ public class Round {
this.failReason = null;
}
public Round(String roundId, Round copyRound) {
this(roundId, copyRound.getDenomination(), copyRound.getFees(), copyRound.getMinMustMix(), copyRound.getTargetAnonymitySetInitial(), copyRound.getMinAnonymitySet(), copyRound.getMaxAnonymitySet(), copyRound.getTimeoutAdjustAnonymitySet(), copyRound.getLiquidityTimeout());
public Mix(String mixId, Mix copyMix) {
this(mixId, copyMix.getDenomination(), copyMix.getFees(), copyMix.getMinMustMix(), copyMix.getTargetAnonymitySetInitial(), copyMix.getMinAnonymitySet(), copyMix.getMaxAnonymitySet(), copyMix.getTimeoutAdjustAnonymitySet(), copyMix.getLiquidityTimeout());
}
public RoundTO computeRoundTO() {
if (roundTO == null) {
roundTO = new RoundTO();
public MixTO computeMixTO() {
if (mixTO == null) {
mixTO = new MixTO();
}
roundTO.update(this);
return roundTO;
mixTO.update(this);
return mixTO;
}
public boolean hasMinMustMixReached() {
return getNbInputsMustMix() >= getMinMustMix();
}
public String getRoundId() {
return roundId;
public String getMixId() {
return mixId;
}
public Timestamp getTimeStarted() {
return timeStarted;
}
public Map<RoundStatus, Timestamp> getTimeStatus() {
public Map<MixStatus, Timestamp> getTimeStatus() {
return timeStatus;
}
......@@ -142,13 +142,13 @@ public class Round {
return liquidityTimeout;
}
public RoundStatus getRoundStatus() {
return roundStatus;
public MixStatus getMixStatus() {
return mixStatus;
}
public void setRoundStatusAndTime(RoundStatus roundStatus) {
this.roundStatus = roundStatus;
timeStatus.put(roundStatus, new Timestamp(System.currentTimeMillis()));
public void setMixStatusAndTime(MixStatus mixStatus) {
this.mixStatus = mixStatus;
timeStatus.put(mixStatus, new Timestamp(System.currentTimeMillis()));
}
public Collection<RegisteredInput> getInputs() {
......@@ -167,15 +167,15 @@ public class Round {
return (int)getInputs().parallelStream().filter(input -> input.isLiquidity()).count();
}
public synchronized void registerInput(RegisteredInput registeredInput) throws RoundException {
public synchronized void registerInput(RegisteredInput registeredInput) throws MixException {
String inputId = Utils.computeInputId(registeredInput.getInput());
if (inputsById.containsKey(inputId)) {
throw new RoundException("input already registered");
throw new MixException("input already registered");
}
inputsById.put(inputId, registeredInput);
if (timeStatus.get(RoundStatus.REGISTER_INPUT) == null && !registeredInput.isLiquidity()) {
timeStatus.put(RoundStatus.REGISTER_INPUT, new Timestamp(System.currentTimeMillis()));
if (timeStatus.get(MixStatus.REGISTER_INPUT) == null && !registeredInput.isLiquidity()) {
timeStatus.put(MixStatus.REGISTER_INPUT, new Timestamp(System.currentTimeMillis()));
}
}
......
......@@ -23,7 +23,7 @@ public class WhirlpoolServerConfig {
private SigningConfig signing;
private RevealOutputConfig revealOutput;
private BanConfig ban;
private RoundConfig round;
private MixConfig mix;
public SamouraiFeeConfig getSamouraiFees() {
......@@ -102,12 +102,12 @@ public class WhirlpoolServerConfig {
this.ban = ban;
}
public RoundConfig getRound() {
return round;
public MixConfig getMix() {
return mix;
}
public void setRound(RoundConfig round) {
this.round = round;
public void setMix(MixConfig mix) {
this.mix = mix;
}
public static class RegisterInputConfig {
......@@ -179,7 +179,7 @@ public class WhirlpoolServerConfig {
}
}
public static class RoundConfig {
public static class MixConfig {
private long denomination;
private long minerFee;
private int mustMixMin;
......
package com.samourai.whirlpool.server.controllers.v1;
import com.samourai.whirlpool.protocol.WhirlpoolProtocol;
import com.samourai.whirlpool.server.services.RoundService;
import com.samourai.whirlpool.server.services.MixService;
import com.samourai.whirlpool.server.services.WebSocketService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -14,29 +14,28 @@ import java.lang.invoke.MethodHandles;
import java.security.Principal;
@Controller
public class RoundStatusController {
public class MixStatusController {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private RoundService roundService;
private MixService mixService;
private WebSocketService webSocketService;
@Autowired
public RoundStatusController(RoundService roundService, WebSocketService webSocketService) {
this.roundService = roundService;
public MixStatusController(MixService mixService, WebSocketService webSocketService) {
this.mixService = mixService;
this.webSocketService = webSocketService;
}
@SubscribeMapping(WhirlpoolProtocol.SOCKET_SUBSCRIBE_USER_PRIVATE + WhirlpoolProtocol.SOCKET_SUBSCRIBE_USER_REPLY)
public void roundStatusOnSubscribe(Principal principal) {
public void mixStatusOnSubscribe(Principal principal) {
String username = principal.getName();
if (log.isDebugEnabled()) {
log.info("[controller] subscribe:"+ WhirlpoolProtocol.SOCKET_SUBSCRIBE_USER_PRIVATE + WhirlpoolProtocol.SOCKET_SUBSCRIBE_USER_REPLY + ": username=" + username);
}
// return roundStatus
try {
Thread.sleep(1000); // wait to make sure client subscription is ready
this.webSocketService.sendPrivate(username, roundService.computeRoundStatusNotification());
this.webSocketService.sendPrivate(username, mixService.computeMixStatusNotification());
}
catch(Exception e) {
log.error("", e);
......
......@@ -40,7 +40,7 @@ public class RegisterInputController {
try {
// register inputs and send back signed bordereau
registerInputService.registerInput(payload.roundId, username, payload.pubkey, payload.signature, payload.blindedBordereau, payload.utxoHash, payload.utxoIndex, payload.paymentCode, payload.liquidity);
registerInputService.registerInput(payload.mixId, username, payload.pubkey, payload.signature, payload.blindedBordereau, payload.utxoHash, payload.utxoIndex, payload.paymentCode, payload.liquidity);
}
catch(UnconfirmedInputException e) {
log.info("Placing unconfirmed input on queue: " + payload.utxoHash+":"+payload.utxoIndex, e.getMessage());
......
......@@ -35,7 +35,7 @@ public class RegisterOutputController {
validate(payload);
// register output
registerOutputService.registerOutput(payload.roundId, payload.unblindedSignedBordereau, payload.bordereau, payload.sendAddress, payload.receiveAddress);
registerOutputService.registerOutput(payload.mixId, payload.unblindedSignedBordereau, payload.bordereau, payload.sendAddress, payload.receiveAddress);
}
private void validate(RegisterOutputRequest registerOutputRequest) throws IllegalInputException {
......
......@@ -2,7 +2,7 @@ package com.samourai.whirlpool.server.controllers.v1;
import com.samourai.whirlpool.protocol.WhirlpoolProtocol;
import com.samourai.whirlpool.protocol.v1.messages.RegisterOutputRequest;
import com.samourai.whirlpool.server.services.RoundService;
import com.samourai.whirlpool.server.services.MixService;
import com.samourai.whirlpool.server.services.WebSocketService;
import com.samourai.whirlpool.server.utils.Utils;
import org.slf4j.Logger;
......@@ -21,12 +21,12 @@ public class RevealOutputController {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private WebSocketService webSocketService;
private RoundService roundService;
private MixService mixService;
@Autowired
public RevealOutputController(WebSocketService webSocketService, RoundService roundService) {
public RevealOutputController(WebSocketService webSocketService, MixService mixService) {
this.webSocketService = webSocketService;
this.roundService = roundService;
this.mixService = mixService;
}
@MessageMapping(WhirlpoolProtocol.ENDPOINT_REVEAL_OUTPUT)
......@@ -37,7 +37,7 @@ public class RevealOutputController {
}
// register output
roundService.revealOutput(payload.roundId, username, payload.bordereau);
mixService.revealOutput(payload.mixId, username, payload.bordereau);
}
@MessageExceptionHandler
......
......@@ -37,7 +37,7 @@ public class SigningController {
}
// signing
signingService.signing(payload.roundId, username, payload.witness);
signingService.signing(payload.mixId, username, payload.witness);
}
@MessageExceptionHandler
......
......@@ -2,8 +2,8 @@ package com.samourai.whirlpool.server.controllers.web;
import com.google.common.collect.Lists;
import com.samourai.whirlpool.server.config.WhirlpoolServerConfig;
import com.samourai.whirlpool.server.persistence.to.RoundLogTO;
import com.samourai.whirlpool.server.persistence.to.RoundTO;
import com.samourai.whirlpool.server.persistence.to.MixLogTO;
import com.samourai.whirlpool.server.persistence.to.MixTO;
import com.samourai.whirlpool.server.services.DbService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -33,22 +33,22 @@ public class HistoryWebController {
@RequestMapping(value = ENDPOINT, method = RequestMethod.GET)
public String history(Model model, WhirlpoolServerConfig whirlpoolServerConfig) throws Exception {
Iterable<RoundTO> rounds = Lists.newArrayList(dbService.findRounds());
model.addAttribute("rounds", rounds);
Iterable<MixTO> mixs = Lists.newArrayList(dbService.findMixs());
model.addAttribute("mixs", mixs);
model.addAttribute("urlExplorer", computeUrlExplorer());
// getters used in template
if (false) {
for (RoundTO roundTO : rounds) {
roundTO.getRoundId();
roundTO.getAnonymitySet();
roundTO.getNbMustMix();
roundTO.getNbLiquidities();
roundTO.getRoundStatus();
roundTO.getFailReason();
RoundLogTO roundLogTO = roundTO.getRoundLog();
roundLogTO.getTxid();
roundLogTO.getRawTx();
for (MixTO mixTO : mixs) {
mixTO.getMixId();
mixTO.getAnonymitySet();
mixTO.getNbMustMix();
mixTO.getNbLiquidities();
mixTO.getMixStatus();
mixTO.getFailReason();
MixLogTO mixLogTO = mixTO.getMixLog();
mixLogTO.getTxid();
mixLogTO.getRawTx();
}
}
return "history";
......
package com.samourai.whirlpool.server.controllers.web;
import com.samourai.whirlpool.protocol.v1.notifications.RoundStatus;
import com.samourai.whirlpool.protocol.v1.notifications.MixStatus;
import com.samourai.whirlpool.server.beans.LiquidityPool;
import com.samourai.whirlpool.server.beans.Round;
import com.samourai.whirlpool.server.services.RoundLimitsService;
import com.samourai.whirlpool.server.services.RoundService;
import com.samourai.whirlpool.server.beans.Mix;
import com.samourai.whirlpool.server.services.MixLimitsService;
import com.samourai.whirlpool.server.services.MixService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -23,79 +23,79 @@ import java.util.Map;
public class StatusWebController {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
public static final String ENDPOINT = "/status";
private static final String STATUS_START_ROUND = "START_ROUND";
private static final String STATUS_START_MIX = "START_MIX";
private RoundService roundService;
private RoundLimitsService roundLimitsService;
private MixService mixService;
private MixLimitsService mixLimitsService;
@Autowired
public StatusWebController(RoundService roundService, RoundLimitsService roundLimitsService) {
this.roundService = roundService;
this.roundLimitsService = roundLimitsService;
public StatusWebController(MixService mixService, MixLimitsService mixLimitsService) {
this.mixService = mixService;
this.mixLimitsService = mixLimitsService;
}
@RequestMapping(value = ENDPOINT, method = RequestMethod.GET)
public String status(Model model) throws Exception {
Round round = roundService.__getCurrentRound();
model.addAttribute("roundId", round.getRoundId());
model.addAttribute("roundStatus", round.getNbInputsMustMix() > 0 ? round.getRoundStatus() : STATUS_START_ROUND);
model.addAttribute("targetAnonymitySet", round.getTargetAnonymitySet());
model.addAttribute("maxAnonymitySet", round.getMaxAnonymitySet());
model.addAttribute("minAnonymitySet", round.getMinAnonymitySet());
model.addAttribute("nbInputs", round.getNbInputs());
model.addAttribute("nbInputsMustMix", round.getNbInputsMustMix());
model.addAttribute("nbInputsLiquidities", round.getNbInputsLiquidities());
Long currentStepElapsedTime = toSeconds(this.roundLimitsService.getLimitsWatcherElapsedTime(round));
Long currentStepRemainingTime = toSeconds(this.roundLimitsService.getLimitsWatcherTimeToWait(round));
Mix mix = mixService.__getCurrentMix();
model.addAttribute("mixId", mix.getMixId());
model.addAttribute("mixStatus", mix.getNbInputsMustMix() > 0 ? mix.getMixStatus() : STATUS_START_MIX);
model.addAttribute("targetAnonymitySet", mix.getTargetAnonymitySet());
model.addAttribute("maxAnonymitySet", mix.getMaxAnonymitySet());
model.addAttribute("minAnonymitySet", mix.getMinAnonymitySet());
model.addAttribute("nbInputs", mix.getNbInputs());
model.addAttribute("nbInputsMustMix", mix.getNbInputsMustMix());
model.addAttribute("nbInputsLiquidities", mix.getNbInputsLiquidities());
Long currentStepElapsedTime = toSeconds(this.mixLimitsService.getLimitsWatcherElapsedTime(mix));
Long currentStepRemainingTime = toSeconds(this.mixLimitsService.getLimitsWatcherTimeToWait(mix));
Double currentStepProgress = currentStepElapsedTime != null && currentStepRemainingTime != null ? Math.ceil(Double.valueOf(currentStepElapsedTime) / (currentStepElapsedTime+currentStepRemainingTime) * 100) : null;
model.addAttribute("currentStepProgress", currentStepProgress);
String currentStepProgressLabel = computeCurrentStepProgressLabel(round.getRoundStatus(), currentStepElapsedTime, currentStepRemainingTime);
String currentStepProgressLabel = computeCurrentStepProgressLabel(mix.getMixStatus(), currentStepElapsedTime, currentStepRemainingTime);
model.addAttribute("currentStepProgressLabel", currentStepProgressLabel);
LiquidityPool liquidityPool = roundLimitsService.getLiquidityPool(round);
LiquidityPool liquidityPool = mixLimitsService.getLiquidityPool(mix);
model.addAttribute("nbLiquiditiesAvailable", liquidityPool.getNbLiquidities());
Map<RoundStatus, Timestamp> timeStatus = round.getTimeStatus();
Map<MixStatus, Timestamp> timeStatus = mix.getTimeStatus();
List<StatusStep> steps = new ArrayList<>();
steps.add(new StatusStep(!timeStatus.isEmpty(), timeStatus.isEmpty(), STATUS_START_ROUND, null));
steps.add(computeStep(RoundStatus.REGISTER_INPUT, timeStatus));
steps.add(computeStep(RoundStatus.REGISTER_OUTPUT, timeStatus));
if (timeStatus.containsKey(RoundStatus.REVEAL_OUTPUT_OR_BLAME)) {
steps.add(computeStep(RoundStatus.REVEAL_OUTPUT_OR_BLAME, timeStatus));
steps.add(computeStep(RoundStatus.FAIL, timeStatus));
steps.add(new StatusStep(!timeStatus.isEmpty(), timeStatus.isEmpty(), STATUS_START_MIX, null));
steps.add(computeStep(MixStatus.REGISTER_INPUT, timeStatus));
steps.add(computeStep(MixStatus.REGISTER_OUTPUT, timeStatus));
if (timeStatus.containsKey(MixStatus.REVEAL_OUTPUT)) {
steps.add(computeStep(MixStatus.REVEAL_OUTPUT, timeStatus));
steps.add(computeStep(MixStatus.FAIL, timeStatus));
}
else {
steps.add(computeStep(RoundStatus.SIGNING, timeStatus));
if (timeStatus.containsKey(RoundStatus.FAIL)) {
steps.add(computeStep(RoundStatus.FAIL, timeStatus));
steps.add(computeStep(MixStatus.SIGNING, timeStatus));
if (timeStatus.containsKey(MixStatus.FAIL)) {
steps.add(computeStep(MixStatus.FAIL, timeStatus));
}
else {
steps.add(computeStep(RoundStatus.SUCCESS, timeStatus));
steps.add(computeStep(MixStatus.SUCCESS, timeStatus));
}
}
model.addAttribute("steps", steps);
List<StatusEvent> events = new ArrayList<>();
events.add(new StatusEvent(round.getTimeStarted(), STATUS_START_ROUND, null));
timeStatus.forEach(((roundStatus, timestamp) -> events.add(new StatusEvent(timestamp, roundStatus.toString(), null))));
events.add(new StatusEvent(mix.getTimeStarted(), STATUS_START_MIX, null));
timeStatus.forEach(((mixStatus, timestamp) -> events.add(new StatusEvent(timestamp, mixStatus.toString(), null))));
model.addAttribute("events", events);
return "status";
}
private StatusStep computeStep(RoundStatus roundStatus, Map<RoundStatus, Timestamp> timeStatus) {
boolean isActive = (!timeStatus.isEmpty() && new ArrayList<>(timeStatus.keySet()).indexOf(roundStatus) == (timeStatus.size()-1));
boolean isDone = !isActive && timeStatus.containsKey(roundStatus);
return new StatusStep(isDone, isActive, roundStatus.toString(), null);
private StatusStep computeStep(MixStatus mixStatus, Map<MixStatus, Timestamp> timeStatus) {
boolean isActive = (!timeStatus.isEmpty() && new ArrayList<>(timeStatus.keySet()).indexOf(mixStatus) == (timeStatus.size()-1));
boolean isDone = !isActive && timeStatus.containsKey(mixStatus);
return new StatusStep(isDone, isActive, mixStatus.toString(), null);
}
private String computeCurrentStepProgressLabel(RoundStatus roundStatus, Long currentStepElapsedTime, Long currentStepRemainingTime) {
private String computeCurrentStepProgressLabel(MixStatus mixStatus, Long currentStepElapsedTime, Long currentStepRemainingTime) {
String progressLabel = null;
if (currentStepElapsedTime != null && currentStepRemainingTime != null) {
progressLabel = currentStepElapsedTime + "s elapsed, " + currentStepRemainingTime + "s remaining ";
switch (roundStatus) {
switch (mixStatus) {
case REGISTER_INPUT:
progressLabel += "before anonymitySet adjustment";
break;
......@@ -105,7 +105,7 @@ public class StatusWebController {
case SIGNING:
progressLabel += "to sign";
break;
case REVEAL_OUTPUT_OR_BLAME:
case REVEAL_OUTPUT:
progressLabel += "to reveal outputs";
break;
}
......
package com.samourai.whirlpool.server.exceptions;
public class RoundException extends Exception {
public class MixException extends Exception {
public RoundException(String message) {
public MixException(String message) {
super(message);
}
}
package com.samourai.whirlpool.server.persistence.repositories;
import com.samourai.whirlpool.server.persistence.to.RoundTO;
import com.samourai.whirlpool.server.persistence.to.MixTO;
import org.springframework.data.repository.PagingAndSortingRepository;
public interface RoundRepository extends PagingAndSortingRepository<RoundTO, Long> {
public interface MixRepository extends PagingAndSortingRepository<MixTO, Long> {
......
......@@ -14,22 +14,22 @@ public class BlameTO extends EntityTO {
@Enumerated(EnumType.STRING)
private BlameReason blameReason;
private String roundId;
private String mixId;
public BlameTO() {
}
public BlameTO(RegisteredInput registeredInput, BlameReason blameReason, String roundId) {
public BlameTO(RegisteredInput registeredInput, BlameReason blameReason, String mixId) {
super();
this.blameReason = blameReason;
this.roundId = roundId;
this.mixId = mixId;
}
public BlameReason getBlameReason() {
return blameReason;