Skip to content

Commit 4d4c9e3

Browse files
Merge pull request bisq-network#7000 from alvasw/txvalidator_fix_crash_on_invalid_vin_vout_json_array
TxValidator: Fix crash on invalid vin or vout JSON Array
2 parents 5872e9e + d7cd1e2 commit 4d4c9e3

File tree

3 files changed

+185
-12
lines changed

3 files changed

+185
-12
lines changed

core/src/main/java/bisq/core/provider/mempool/TxValidator.java

+10-5
Original file line numberDiff line numberDiff line change
@@ -302,12 +302,17 @@ private static Tuple2<JsonArray, JsonArray> getVinAndVout(String jsonTxt) throws
302302
if (json.get("vin") == null || json.get("vout") == null) {
303303
throw new JsonSyntaxException("missing vin/vout");
304304
}
305-
JsonArray jsonVin = json.get("vin").getAsJsonArray();
306-
JsonArray jsonVout = json.get("vout").getAsJsonArray();
307-
if (jsonVin == null || jsonVout == null || jsonVin.size() < 1 || jsonVout.size() < 2) {
308-
throw new JsonSyntaxException("not enough vins/vouts");
305+
306+
try {
307+
JsonArray jsonVin = json.get("vin").getAsJsonArray();
308+
JsonArray jsonVout = json.get("vout").getAsJsonArray();
309+
if (jsonVin == null || jsonVout == null || jsonVin.size() < 1 || jsonVout.size() < 2) {
310+
throw new JsonSyntaxException("not enough vins/vouts");
311+
}
312+
return new Tuple2<>(jsonVin, jsonVout);
313+
} catch (IllegalStateException e) {
314+
throw new JsonSyntaxException("vin/vout no as JSON Array", e);
309315
}
310-
return new Tuple2<>(jsonVin, jsonVout);
311316
}
312317

313318
private static FeeValidationStatus initialSanityChecks(String txId, String jsonTxt) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
* This file is part of Bisq.
3+
*
4+
* Bisq is free software: you can redistribute it and/or modify it
5+
* under the terms of the GNU Affero General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or (at
7+
* your option) any later version.
8+
*
9+
* Bisq is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12+
* License for more details.
13+
*
14+
* You should have received a copy of the GNU Affero General Public License
15+
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package bisq.core.fee;
19+
20+
import bisq.core.dao.state.DaoStateService;
21+
import bisq.core.filter.FilterManager;
22+
import bisq.core.provider.mempool.FeeValidationStatus;
23+
import bisq.core.provider.mempool.TxValidator;
24+
25+
import com.google.gson.Gson;
26+
import com.google.gson.JsonObject;
27+
import com.google.gson.JsonPrimitive;
28+
29+
import java.net.URL;
30+
31+
import java.nio.file.Files;
32+
import java.nio.file.Path;
33+
34+
import java.io.IOException;
35+
36+
import java.util.List;
37+
import java.util.Objects;
38+
39+
import org.mockito.Mock;
40+
import org.mockito.junit.jupiter.MockitoExtension;
41+
42+
import org.junit.jupiter.api.BeforeEach;
43+
import org.junit.jupiter.api.Test;
44+
import org.junit.jupiter.api.extension.ExtendWith;
45+
import org.junit.jupiter.params.ParameterizedTest;
46+
import org.junit.jupiter.params.provider.NullAndEmptySource;
47+
import org.junit.jupiter.params.provider.ValueSource;
48+
49+
import static org.hamcrest.CoreMatchers.equalTo;
50+
import static org.hamcrest.CoreMatchers.is;
51+
import static org.hamcrest.MatcherAssert.assertThat;
52+
import static org.junit.jupiter.api.Assertions.assertThrows;
53+
54+
@ExtendWith(MockitoExtension.class)
55+
public class MakerTxValidatorSanityCheckTests {
56+
public static final List<String> FEE_RECEIVER_ADDRESSES = List.of("2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w");
57+
58+
private TxValidator txValidator;
59+
60+
@BeforeEach
61+
void setup(@Mock DaoStateService daoStateService, @Mock FilterManager filterManager) {
62+
String txId = "e3607e971ead7d03619e3a9eeaa771ed5adba14c448839e0299f857f7bb4ec07";
63+
txValidator = new TxValidator(daoStateService, txId, filterManager);
64+
}
65+
66+
@ParameterizedTest
67+
@NullAndEmptySource
68+
void nullAndEmptyMempoolResponse(String jsonText) {
69+
TxValidator txValidator1 = txValidator.parseJsonValidateMakerFeeTx(jsonText, FEE_RECEIVER_ADDRESSES);
70+
FeeValidationStatus status = txValidator1.getStatus();
71+
assertThat(status, is(equalTo(FeeValidationStatus.NACK_JSON_ERROR)));
72+
}
73+
74+
@Test
75+
void invalidJsonResponse() {
76+
String invalidJson = "in\"valid'json',";
77+
TxValidator txValidator1 = txValidator.parseJsonValidateMakerFeeTx(invalidJson, FEE_RECEIVER_ADDRESSES);
78+
79+
FeeValidationStatus status = txValidator1.getStatus();
80+
assertThat(status, is(equalTo(FeeValidationStatus.NACK_JSON_ERROR)));
81+
}
82+
83+
@ParameterizedTest
84+
@ValueSource(strings = {"status", "txid", "vin", "vout"})
85+
void mempoolResponseWithMissingField(String missingField) throws IOException {
86+
JsonObject json = getValidBtcMakerFeeMempoolJsonResponse();
87+
json.remove(missingField);
88+
assertThat(json.has(missingField), is(false));
89+
90+
String jsonContent = new Gson().toJson(json);
91+
TxValidator txValidator1 = txValidator.parseJsonValidateMakerFeeTx(jsonContent, FEE_RECEIVER_ADDRESSES);
92+
93+
FeeValidationStatus status = txValidator1.getStatus();
94+
assertThat(status, is(equalTo(FeeValidationStatus.NACK_JSON_ERROR)));
95+
}
96+
97+
@Test
98+
void mempoolResponseWithoutConfirmedField() throws IOException {
99+
JsonObject json = getValidBtcMakerFeeMempoolJsonResponse();
100+
json.get("status").getAsJsonObject().remove("confirmed");
101+
assertThat(json.get("status").getAsJsonObject().has("confirmed"), is(false));
102+
103+
String jsonContent = new Gson().toJson(json);
104+
TxValidator txValidator1 = txValidator.parseJsonValidateMakerFeeTx(jsonContent, FEE_RECEIVER_ADDRESSES);
105+
106+
FeeValidationStatus status = txValidator1.getStatus();
107+
assertThat(status, is(equalTo(FeeValidationStatus.NACK_JSON_ERROR)));
108+
}
109+
110+
@ParameterizedTest
111+
@ValueSource(strings = {"vin", "vout"})
112+
void checkFeeAddressBtcTestVinOrVoutNotJsonArray(String vinOrVout) throws IOException {
113+
JsonObject json = MakerTxValidatorSanityCheckTests.getValidBtcMakerFeeMempoolJsonResponse();
114+
json.add(vinOrVout, new JsonPrimitive(1234));
115+
assertThrows(IllegalStateException.class, () -> json.get(vinOrVout).getAsJsonArray());
116+
117+
String jsonContent = new Gson().toJson(json);
118+
TxValidator txValidator1 = txValidator.parseJsonValidateMakerFeeTx(jsonContent,
119+
MakerTxValidatorSanityCheckTests.FEE_RECEIVER_ADDRESSES);
120+
121+
assertThat(txValidator1.getStatus(), is(FeeValidationStatus.NACK_JSON_ERROR));
122+
}
123+
124+
@Test
125+
void responseHasDifferentTxId() throws IOException {
126+
String differentTxId = "abcde971ead7d03619e3a9eeaa771ed5adba14c448839e0299f857f7bb4ec07";
127+
128+
JsonObject json = getValidBtcMakerFeeMempoolJsonResponse();
129+
json.add("txid", new JsonPrimitive(differentTxId));
130+
assertThat(json.get("txid").getAsString(), is(differentTxId));
131+
132+
String jsonContent = new Gson().toJson(json);
133+
TxValidator txValidator1 = txValidator.parseJsonValidateMakerFeeTx(jsonContent, FEE_RECEIVER_ADDRESSES);
134+
135+
FeeValidationStatus status = txValidator1.getStatus();
136+
assertThat(status, is(equalTo(FeeValidationStatus.NACK_JSON_ERROR)));
137+
}
138+
139+
public static JsonObject getValidBtcMakerFeeMempoolJsonResponse() throws IOException {
140+
URL resource = MakerTxValidatorSanityCheckTests.class.getClassLoader()
141+
.getResource("mempool_test_data/valid_btc_maker_fee.json");
142+
String path = Objects.requireNonNull(resource).getPath();
143+
144+
if (System.getProperty("os.name").toLowerCase().startsWith("win")) {
145+
// We need to remove the first character on Windows because the path starts with a
146+
// leading slash "/C:/Users/..."
147+
path = path.substring(1);
148+
}
149+
150+
String jsonContent = Files.readString(Path.of(path));
151+
return new Gson().fromJson(jsonContent, JsonObject.class);
152+
}
153+
}

core/src/test/java/bisq/core/fee/TxValidatorSanityCheckTests.java core/src/test/java/bisq/core/fee/TakerTxValidatorSanityCheckTests.java

+22-7
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@
4949
import static org.hamcrest.CoreMatchers.equalTo;
5050
import static org.hamcrest.CoreMatchers.is;
5151
import static org.hamcrest.MatcherAssert.assertThat;
52+
import static org.junit.jupiter.api.Assertions.assertThrows;
5253

5354
@ExtendWith(MockitoExtension.class)
54-
public class TxValidatorSanityCheckTests {
55+
public class TakerTxValidatorSanityCheckTests {
5556
private final List<String> FEE_RECEIVER_ADDRESSES = List.of("2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w");
5657

5758
private TxValidator txValidator;
@@ -65,29 +66,29 @@ void setup(@Mock DaoStateService daoStateService, @Mock FilterManager filterMana
6566
@ParameterizedTest
6667
@NullAndEmptySource
6768
void nullAndEmptyMempoolResponse(String jsonText) {
68-
TxValidator txValidator1 = txValidator.parseJsonValidateMakerFeeTx(jsonText, FEE_RECEIVER_ADDRESSES);
69+
TxValidator txValidator1 = txValidator.parseJsonValidateTakerFeeTx(jsonText, FEE_RECEIVER_ADDRESSES);
6970
FeeValidationStatus status = txValidator1.getStatus();
7071
assertThat(status, is(equalTo(FeeValidationStatus.NACK_JSON_ERROR)));
7172
}
7273

7374
@Test
7475
void invalidJsonResponse() {
7576
String invalidJson = "in\"valid'json',";
76-
TxValidator txValidator1 = txValidator.parseJsonValidateMakerFeeTx(invalidJson, FEE_RECEIVER_ADDRESSES);
77+
TxValidator txValidator1 = txValidator.parseJsonValidateTakerFeeTx(invalidJson, FEE_RECEIVER_ADDRESSES);
7778

7879
FeeValidationStatus status = txValidator1.getStatus();
7980
assertThat(status, is(equalTo(FeeValidationStatus.NACK_JSON_ERROR)));
8081
}
8182

8283
@ParameterizedTest
83-
@ValueSource(strings = {"status", "txid"})
84+
@ValueSource(strings = {"status", "txid", "vin", "vout"})
8485
void mempoolResponseWithMissingField(String missingField) throws IOException {
8586
JsonObject json = getValidBtcMakerFeeMempoolJsonResponse();
8687
json.remove(missingField);
8788
assertThat(json.has(missingField), is(false));
8889

8990
String jsonContent = new Gson().toJson(json);
90-
TxValidator txValidator1 = txValidator.parseJsonValidateMakerFeeTx(jsonContent, FEE_RECEIVER_ADDRESSES);
91+
TxValidator txValidator1 = txValidator.parseJsonValidateTakerFeeTx(jsonContent, FEE_RECEIVER_ADDRESSES);
9192

9293
FeeValidationStatus status = txValidator1.getStatus();
9394
assertThat(status, is(equalTo(FeeValidationStatus.NACK_JSON_ERROR)));
@@ -100,12 +101,26 @@ void mempoolResponseWithoutConfirmedField() throws IOException {
100101
assertThat(json.get("status").getAsJsonObject().has("confirmed"), is(false));
101102

102103
String jsonContent = new Gson().toJson(json);
103-
TxValidator txValidator1 = txValidator.parseJsonValidateMakerFeeTx(jsonContent, FEE_RECEIVER_ADDRESSES);
104+
TxValidator txValidator1 = txValidator.parseJsonValidateTakerFeeTx(jsonContent, FEE_RECEIVER_ADDRESSES);
104105

105106
FeeValidationStatus status = txValidator1.getStatus();
106107
assertThat(status, is(equalTo(FeeValidationStatus.NACK_JSON_ERROR)));
107108
}
108109

110+
@ParameterizedTest
111+
@ValueSource(strings = {"vin", "vout"})
112+
void checkFeeAddressBtcTestVinOrVoutNotJsonArray(String vinOrVout) throws IOException {
113+
JsonObject json = MakerTxValidatorSanityCheckTests.getValidBtcMakerFeeMempoolJsonResponse();
114+
json.add(vinOrVout, new JsonPrimitive(1234));
115+
assertThrows(IllegalStateException.class, () -> json.get(vinOrVout).getAsJsonArray());
116+
117+
String jsonContent = new Gson().toJson(json);
118+
TxValidator txValidator1 = txValidator.parseJsonValidateMakerFeeTx(jsonContent,
119+
MakerTxValidatorSanityCheckTests.FEE_RECEIVER_ADDRESSES);
120+
121+
assertThat(txValidator1.getStatus(), is(FeeValidationStatus.NACK_JSON_ERROR));
122+
}
123+
109124
@Test
110125
void responseHasDifferentTxId() throws IOException {
111126
String differentTxId = "abcde971ead7d03619e3a9eeaa771ed5adba14c448839e0299f857f7bb4ec07";
@@ -115,7 +130,7 @@ void responseHasDifferentTxId() throws IOException {
115130
assertThat(json.get("txid").getAsString(), is(differentTxId));
116131

117132
String jsonContent = new Gson().toJson(json);
118-
TxValidator txValidator1 = txValidator.parseJsonValidateMakerFeeTx(jsonContent, FEE_RECEIVER_ADDRESSES);
133+
TxValidator txValidator1 = txValidator.parseJsonValidateTakerFeeTx(jsonContent, FEE_RECEIVER_ADDRESSES);
119134

120135
FeeValidationStatus status = txValidator1.getStatus();
121136
assertThat(status, is(equalTo(FeeValidationStatus.NACK_JSON_ERROR)));

0 commit comments

Comments
 (0)