Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature enabling log file upload to mediators #5953

Merged
merged 4 commits into from Jan 12, 2022
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
14 changes: 13 additions & 1 deletion common/src/main/java/bisq/common/file/FileUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.nio.file.Paths;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
Expand All @@ -43,6 +44,7 @@
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -128,7 +130,7 @@ public static void deleteDirectory(File file,
try {
deleteFileIfExists(file, ignoreLockedFiles);
} catch (Throwable t) {
log.error("Could not delete file. Error=" + t.toString());
log.error("Could not delete file. Error=" + t);
throw new IOException(t);
}
}
Expand Down Expand Up @@ -259,4 +261,14 @@ public static void removeAndBackupFile(File dbDir, File storageFile, String file
renameFile(storageFile, corruptedFile);
}
}

public static boolean doesFileContainKeyword(File file, String keyword) throws FileNotFoundException {
Scanner s = new Scanner(file);
while (s.hasNextLine()) {
if (s.nextLine().contains(keyword)) {
return true;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
import bisq.network.p2p.AckMessage;
import bisq.network.p2p.BundleOfEnvelopes;
import bisq.network.p2p.CloseConnectionMessage;
import bisq.network.p2p.FileTransferPart;
import bisq.network.p2p.PrefixedSealedAndSignedMessage;
import bisq.network.p2p.peers.getdata.messages.GetDataResponse;
import bisq.network.p2p.peers.getdata.messages.GetUpdatedDataRequest;
Expand Down Expand Up @@ -131,6 +132,8 @@ public NetworkEnvelope fromProto(protobuf.NetworkEnvelope proto) throws Protobuf
return Ping.fromProto(proto.getPing(), messageVersion);
case PONG:
return Pong.fromProto(proto.getPong(), messageVersion);
case FILE_TRANSFER_PART:
return FileTransferPart.fromProto(proto.getFileTransferPart(), messageVersion);

case OFFER_AVAILABILITY_REQUEST:
return OfferAvailabilityRequest.fromProto(proto.getOfferAvailabilityRequest(), messageVersion);
Expand Down
27 changes: 27 additions & 0 deletions core/src/main/java/bisq/core/support/dispute/Dispute.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@
import bisq.core.locale.Res;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.support.SupportType;
import bisq.core.support.dispute.mediation.FileTransferReceiver;
import bisq.core.support.dispute.mediation.FileTransferSender;
import bisq.core.support.dispute.mediation.FileTransferSession;
import bisq.core.support.messages.ChatMessage;
import bisq.core.trade.model.bisq_v1.Contract;

import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.network.NetworkNode;

import bisq.common.crypto.PubKeyRing;
import bisq.common.proto.ProtoUtil;
import bisq.common.proto.network.NetworkPayload;
Expand All @@ -46,6 +52,8 @@
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import java.io.IOException;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
Expand Down Expand Up @@ -154,6 +162,20 @@ public static protobuf.Dispute.State toProtoMessage(Dispute.State state) {
private transient final BooleanProperty isClosedProperty = new SimpleBooleanProperty();
private transient final IntegerProperty badgeCountProperty = new SimpleIntegerProperty();

private transient FileTransferReceiver fileTransferSession = null;

public FileTransferReceiver createOrGetFileTransferReceiver(NetworkNode networkNode, NodeAddress peerNodeAddress, FileTransferSession.FtpCallback callback) throws IOException {
// the receiver stores its state temporarily here in the dispute
// this method gets called to retrieve the session each time a part of the log files is received
if (fileTransferSession == null) {
fileTransferSession = new FileTransferReceiver(networkNode, peerNodeAddress, this.tradeId, this.traderId, this.getRoleStringForLogFile(), callback);
}
return fileTransferSession;
}

public FileTransferSender createFileTransferSender(NetworkNode networkNode, NodeAddress peerNodeAddress, FileTransferSession.FtpCallback callback) {
return new FileTransferSender(networkNode, peerNodeAddress, this.tradeId, this.traderId, this.getRoleStringForLogFile(), callback);
}

///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
Expand Down Expand Up @@ -441,6 +463,11 @@ public String getRoleString() {
}
}

public String getRoleStringForLogFile() {
return (disputeOpenerIsBuyer ? "BUYER" : "SELLER") + "_"
+ (disputeOpenerIsMaker ? "MAKER" : "TAKER");
}

@Override
public String toString() {
return "Dispute{" +
Expand Down
35 changes: 24 additions & 11 deletions core/src/main/java/bisq/core/support/dispute/DisputeManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public void requestPersistence() {
@Override
public NodeAddress getPeerNodeAddress(ChatMessage message) {
Optional<Dispute> disputeOptional = findDispute(message);
if (!disputeOptional.isPresent()) {
if (disputeOptional.isEmpty()) {
log.warn("Could not find dispute for tradeId = {} traderId = {}",
message.getTradeId(), message.getTraderId());
return null;
Expand All @@ -156,7 +156,7 @@ public NodeAddress getPeerNodeAddress(ChatMessage message) {
@Override
public PubKeyRing getPeerPubKeyRing(ChatMessage message) {
Optional<Dispute> disputeOptional = findDispute(message);
if (!disputeOptional.isPresent()) {
if (disputeOptional.isEmpty()) {
log.warn("Could not find dispute for tradeId = {} traderId = {}",
message.getTradeId(), message.getTraderId());
return null;
Expand Down Expand Up @@ -325,7 +325,7 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa
if (isAgent(dispute)) {
if (!disputeList.contains(dispute)) {
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
if (!storedDisputeOptional.isPresent()) {
if (storedDisputeOptional.isEmpty()) {
disputeList.add(dispute);
sendPeerOpenedDisputeMessage(dispute, contract, peersPubKeyRing);
} else {
Expand Down Expand Up @@ -378,7 +378,7 @@ protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDis
Dispute dispute = peerOpenedDisputeMessage.getDispute();

Optional<Trade> optionalTrade = tradeManager.getTradeById(dispute.getTradeId());
if (!optionalTrade.isPresent()) {
if (optionalTrade.isEmpty()) {
return;
}

Expand All @@ -399,11 +399,10 @@ protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDis
if (!isAgent(dispute)) {
if (!disputeList.contains(dispute)) {
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
if (!storedDisputeOptional.isPresent()) {
if (storedDisputeOptional.isEmpty()) {
disputeList.add(dispute);
trade.setDisputeState(getDisputeStateStartedByPeer());
tradeManager.requestPersistence();
errorMessage = null;
} else {
// valid case if both have opened a dispute and agent was not online.
log.debug("We got a dispute already open for that trade and trading peer. TradeId = {}",
Expand Down Expand Up @@ -452,7 +451,7 @@ public void sendOpenNewDisputeMessage(Dispute dispute,
}

Optional<Dispute> storedDisputeOptional = findDispute(dispute);
if (!storedDisputeOptional.isPresent() || reOpen) {
if (storedDisputeOptional.isEmpty() || reOpen) {
String disputeInfo = getDisputeInfo(dispute);
String disputeMessage = getDisputeIntroForDisputeCreator(disputeInfo);
String sysMsg = dispute.isSupportTicket() ?
Expand Down Expand Up @@ -794,7 +793,7 @@ private Tuple2<NodeAddress, PubKeyRing> getNodeAddressPubKeyRingTuple(Dispute di
return new Tuple2<>(peerNodeAddress, receiverPubKeyRing);
}

private boolean isAgent(Dispute dispute) {
public boolean isAgent(Dispute dispute) {
return pubKeyRing.equals(dispute.getAgentPubKeyRing());
}

Expand All @@ -812,7 +811,7 @@ private Optional<Dispute> findDispute(ChatMessage message) {
return findDispute(message.getTradeId(), message.getTraderId());
}

private Optional<Dispute> findDispute(String tradeId, int traderId) {
protected Optional<Dispute> findDispute(String tradeId, int traderId) {
T disputeList = getDisputeList();
if (disputeList == null) {
log.warn("disputes is null");
Expand All @@ -836,7 +835,7 @@ public Optional<Dispute> findDispute(String tradeId) {

public Optional<Trade> findTrade(Dispute dispute) {
Optional<Trade> retVal = tradeManager.getTradeById(dispute.getTradeId());
if (!retVal.isPresent()) {
if (retVal.isEmpty()) {
retVal = closedTradableManager.getClosedTrades().stream().filter(e -> e.getId().equals(dispute.getTradeId())).findFirst();
}
return retVal;
Expand Down Expand Up @@ -873,6 +872,20 @@ public void addMediationReOpenedMessage(Dispute dispute, boolean senderIsTrader)
requestPersistence();
}

protected void addMediationLogsReceivedMessage(Dispute dispute, String logsIdentifier) {
String logsReceivedMessage = Res.get("support.mediatorReceivedLogs", logsIdentifier);
ChatMessage chatMessage = new ChatMessage(
getSupportType(),
dispute.getTradeId(),
pubKeyRing.hashCode(),
false,
logsReceivedMessage,
p2PService.getAddress());
chatMessage.setSystemMessage(true);
dispute.addAndPersistChatMessage(chatMessage);
requestPersistence();
}

// If price was going down between take offer time and open dispute time the buyer has an incentive to
// not send the payment but to try to make a new trade with the better price. We risks to lose part of the
// security deposit (in mediation we will always get back 0.003 BTC to keep some incentive to accept mediated
Expand Down Expand Up @@ -958,7 +971,7 @@ private Price getPrice(String currencyCode) {
long roundedToLong = MathUtils.roundDoubleToLong(scaled);
return Price.valueOf(currencyCode, roundedToLong);
} catch (Exception e) {
log.error("Exception at getPrice / parseToFiat: " + e.toString());
log.error("Exception at getPrice / parseToFiat: " + e);
return null;
}
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.core.support.dispute.mediation;

import bisq.network.p2p.AckMessage;
import bisq.network.p2p.AckMessageSourceType;
import bisq.network.p2p.FileTransferPart;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.network.NetworkNode;

import bisq.common.UserThread;
import bisq.common.config.Config;
import bisq.common.util.Utilities;

import java.nio.file.FileSystems;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;

import javax.annotation.Nullable;

@Slf4j
public class FileTransferReceiver extends FileTransferSession {
protected final String zipFilePath;

public FileTransferReceiver(NetworkNode networkNode, NodeAddress peerNodeAddress,
String tradeId, int traderId, String traderRole, @Nullable FileTransferSession.FtpCallback callback) throws IOException {
super(networkNode, peerNodeAddress, tradeId, traderId, traderRole, callback);
zipFilePath = ensureReceivingDirectoryExists().getAbsolutePath() + FileSystems.getDefault().getSeparator() + zipId + ".zip";
}

public void processFilePartReceived(FileTransferPart ftp) {
checkpointLastActivity();
// check that the supplied sequence number is in line with what we are expecting
if (currentBlockSeqNum < 0) {
// we have not yet started receiving a file, validate this ftp packet as the initiation request
initReceiveSession(ftp.uid, ftp.seqNumOrFileLength);
} else if (currentBlockSeqNum == ftp.seqNumOrFileLength) {
// we are in the middle of receiving a file; add the block of data to the file
processReceivedBlock(ftp, networkNode, peerNodeAddress);
} else {
log.error("ftp sequence num mismatch, expected {} received {}", currentBlockSeqNum, ftp.seqNumOrFileLength);
resetSession(); // aborts the file transfer
}
}

public void initReceiveSession(String uid, long expectedFileBytes) {
networkNode.addMessageListener(this);
this.expectedFileLength = expectedFileBytes;
fileOffsetBytes = 0;
currentBlockSeqNum = 0;
initSessionTimer();
log.info("Received a start file transfer request, tradeId={}, traderId={}, size={}", fullTradeId, traderId, expectedFileBytes);
log.info("New file will be written to {}", zipFilePath);
UserThread.execute(() -> ackReceivedPart(uid, networkNode, peerNodeAddress));
}

private void processReceivedBlock(FileTransferPart ftp, NetworkNode networkNode, NodeAddress peerNodeAddress) {
try {
RandomAccessFile file = new RandomAccessFile(zipFilePath, "rwd");
file.seek(fileOffsetBytes);
file.write(ftp.messageData.toByteArray(), 0, ftp.messageData.size());
fileOffsetBytes = fileOffsetBytes + ftp.messageData.size();
log.info("Sequence number {} for {}, received data {} / {}",
ftp.seqNumOrFileLength, Utilities.getShortId(ftp.tradeId), fileOffsetBytes, expectedFileLength);
currentBlockSeqNum++;
UserThread.runAfter(() -> {
ackReceivedPart(ftp.uid, networkNode, peerNodeAddress);
if (fileOffsetBytes >= expectedFileLength) {
log.info("Success! We have reached the EOF, received {} expected {}", fileOffsetBytes, expectedFileLength);
ftpCallback.ifPresent(c -> c.onFtpComplete(this));
resetSession();
}
}, 100, TimeUnit.MILLISECONDS);
} catch (IOException e) {
log.error(e.toString());
e.printStackTrace();
}
}

private void ackReceivedPart(String uid, NetworkNode networkNode, NodeAddress peerNodeAddress) {
AckMessage ackMessage = new AckMessage(peerNodeAddress,
AckMessageSourceType.LOG_TRANSFER,
FileTransferPart.class.getSimpleName(),
uid,
Utilities.getShortId(fullTradeId),
true, // result
null); // errorMessage
log.info("Send AckMessage for {} to peer {}. id={}, uid={}",
ackMessage.getSourceMsgClassName(), peerNodeAddress, ackMessage.getSourceId(), ackMessage.getSourceUid());
sendMessage(ackMessage, networkNode, peerNodeAddress);
}

private static File ensureReceivingDirectoryExists() throws IOException {
File directory = new File(Config.appDataDir() + "/clientLogs");
if (!directory.exists() && !directory.mkdirs()) {
log.error("Could not create directory {}", directory.getAbsolutePath());
throw new IOException("Could not create directory: " + directory.getAbsolutePath());
}
return directory;
}
}
Loading