This commit is contained in:
woodser 2021-05-04 20:20:01 -04:00
commit 8a38081c04
2800 changed files with 344130 additions and 0 deletions

View file

@ -0,0 +1,507 @@
/*
* 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.account.sign;
import bisq.core.account.witness.AccountAgeWitness;
import bisq.core.filter.FilterManager;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.network.p2p.P2PService;
import bisq.network.p2p.storage.payload.PersistableNetworkPayload;
import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService;
import bisq.common.crypto.CryptoException;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.Sig;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import com.google.common.base.Charsets;
import java.security.KeyPair;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import org.junit.Before;
import org.junit.Test;
import static bisq.core.account.sign.SignedWitness.VerificationMethod.ARBITRATOR;
import static bisq.core.account.sign.SignedWitness.VerificationMethod.TRADE;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;
public class SignedWitnessServiceTest {
private SignedWitnessService signedWitnessService;
private byte[] account1DataHash;
private byte[] account2DataHash;
private byte[] account3DataHash;
private AccountAgeWitness aew1;
private AccountAgeWitness aew2;
private AccountAgeWitness aew3;
private byte[] signature1;
private byte[] signature2;
private byte[] signature3;
private byte[] signer1PubKey;
private byte[] signer2PubKey;
private byte[] signer3PubKey;
private byte[] witnessOwner1PubKey;
private byte[] witnessOwner2PubKey;
private byte[] witnessOwner3PubKey;
private long date1;
private long date2;
private long date3;
private long tradeAmount1;
private long tradeAmount2;
private long tradeAmount3;
private long SIGN_AGE_1 = SignedWitnessService.SIGNER_AGE_DAYS * 3 + 5;
private long SIGN_AGE_2 = SignedWitnessService.SIGNER_AGE_DAYS * 2 + 4;
private long SIGN_AGE_3 = SignedWitnessService.SIGNER_AGE_DAYS + 3;
private KeyRing keyRing;
private P2PService p2pService;
private FilterManager filterManager;
private ECKey arbitrator1Key;
KeyPair peer1KeyPair;
KeyPair peer2KeyPair;
KeyPair peer3KeyPair;
@Before
public void setup() throws Exception {
AppendOnlyDataStoreService appendOnlyDataStoreService = mock(AppendOnlyDataStoreService.class);
ArbitratorManager arbitratorManager = mock(ArbitratorManager.class);
when(arbitratorManager.isPublicKeyInList(any())).thenReturn(true);
keyRing = mock(KeyRing.class);
p2pService = mock(P2PService.class);
filterManager = mock(FilterManager.class);
signedWitnessService = new SignedWitnessService(keyRing, p2pService, arbitratorManager, null, appendOnlyDataStoreService, null, filterManager);
account1DataHash = org.bitcoinj.core.Utils.sha256hash160(new byte[]{1});
account2DataHash = org.bitcoinj.core.Utils.sha256hash160(new byte[]{2});
account3DataHash = org.bitcoinj.core.Utils.sha256hash160(new byte[]{3});
long account1CreationTime = getTodayMinusNDays(SIGN_AGE_1 + 1);
long account2CreationTime = getTodayMinusNDays(SIGN_AGE_2 + 1);
long account3CreationTime = getTodayMinusNDays(SIGN_AGE_3 + 1);
aew1 = new AccountAgeWitness(account1DataHash, account1CreationTime);
aew2 = new AccountAgeWitness(account2DataHash, account2CreationTime);
aew3 = new AccountAgeWitness(account3DataHash, account3CreationTime);
arbitrator1Key = new ECKey();
peer1KeyPair = Sig.generateKeyPair();
peer2KeyPair = Sig.generateKeyPair();
peer3KeyPair = Sig.generateKeyPair();
signature1 = arbitrator1Key.signMessage(Utilities.encodeToHex(account1DataHash)).getBytes(Charsets.UTF_8);
signature2 = Sig.sign(peer1KeyPair.getPrivate(), Utilities.encodeToHex(account2DataHash).getBytes(Charsets.UTF_8));
signature3 = Sig.sign(peer2KeyPair.getPrivate(), Utilities.encodeToHex(account3DataHash).getBytes(Charsets.UTF_8));
date1 = getTodayMinusNDays(SIGN_AGE_1);
date2 = getTodayMinusNDays(SIGN_AGE_2);
date3 = getTodayMinusNDays(SIGN_AGE_3);
signer1PubKey = arbitrator1Key.getPubKey();
signer2PubKey = Sig.getPublicKeyBytes(peer1KeyPair.getPublic());
signer3PubKey = Sig.getPublicKeyBytes(peer2KeyPair.getPublic());
witnessOwner1PubKey = Sig.getPublicKeyBytes(peer1KeyPair.getPublic());
witnessOwner2PubKey = Sig.getPublicKeyBytes(peer2KeyPair.getPublic());
witnessOwner3PubKey = Sig.getPublicKeyBytes(peer3KeyPair.getPublic());
tradeAmount1 = 1000;
tradeAmount2 = 1001;
tradeAmount3 = 1001;
}
@Test
public void testIsValidAccountAgeWitnessOk() {
SignedWitness sw1 = new SignedWitness(ARBITRATOR, account1DataHash, signature1, signer1PubKey, witnessOwner1PubKey, date1, tradeAmount1);
SignedWitness sw2 = new SignedWitness(TRADE, account2DataHash, signature2, signer2PubKey, witnessOwner2PubKey, date2, tradeAmount2);
SignedWitness sw3 = new SignedWitness(TRADE, account3DataHash, signature3, signer3PubKey, witnessOwner3PubKey, date3, tradeAmount3);
signedWitnessService.addToMap(sw1);
signedWitnessService.addToMap(sw2);
signedWitnessService.addToMap(sw3);
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew3));
}
@Test
public void testIsValidAccountAgeWitnessArbitratorSignatureProblem() {
signature1 = new byte[]{1, 2, 3};
SignedWitness sw1 = new SignedWitness(ARBITRATOR, account1DataHash, signature1, signer1PubKey, witnessOwner1PubKey, date1, tradeAmount1);
SignedWitness sw2 = new SignedWitness(TRADE, account2DataHash, signature2, signer2PubKey, witnessOwner2PubKey, date2, tradeAmount2);
SignedWitness sw3 = new SignedWitness(TRADE, account3DataHash, signature3, signer3PubKey, witnessOwner3PubKey, date3, tradeAmount3);
signedWitnessService.addToMap(sw1);
signedWitnessService.addToMap(sw2);
signedWitnessService.addToMap(sw3);
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
}
@Test
public void testIsValidAccountAgeWitnessPeerSignatureProblem() {
signature2 = new byte[]{1, 2, 3};
SignedWitness sw1 = new SignedWitness(ARBITRATOR, account1DataHash, signature1, signer1PubKey, witnessOwner1PubKey, date1, tradeAmount1);
SignedWitness sw2 = new SignedWitness(TRADE, account2DataHash, signature2, signer2PubKey, witnessOwner2PubKey, date2, tradeAmount2);
SignedWitness sw3 = new SignedWitness(TRADE, account3DataHash, signature3, signer3PubKey, witnessOwner3PubKey, date3, tradeAmount3);
signedWitnessService.addToMap(sw1);
signedWitnessService.addToMap(sw2);
signedWitnessService.addToMap(sw3);
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
}
@Test
public void testIsValidSelfSignatureOk() throws Exception {
KeyPair peer1KeyPair = Sig.generateKeyPair();
signer2PubKey = Sig.getPublicKeyBytes(peer1KeyPair.getPublic());
signature2 = Sig.sign(peer1KeyPair.getPrivate(), Utilities.encodeToHex(account2DataHash).getBytes(Charsets.UTF_8));
signature3 = Sig.sign(peer1KeyPair.getPrivate(), Utilities.encodeToHex(account3DataHash).getBytes(Charsets.UTF_8));
SignedWitness sw1 = new SignedWitness(ARBITRATOR, account1DataHash, signature1, signer1PubKey, signer2PubKey, date1, tradeAmount1);
SignedWitness sw2 = new SignedWitness(TRADE, account2DataHash, signature2, signer2PubKey, signer2PubKey, date2, tradeAmount2);
SignedWitness sw3 = new SignedWitness(TRADE, account3DataHash, signature3, signer2PubKey, signer2PubKey, date3, tradeAmount3);
signedWitnessService.addToMap(sw1);
signedWitnessService.addToMap(sw2);
signedWitnessService.addToMap(sw3);
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew3));
}
@Test
public void testIsValidSimpleLoopSignatureProblem() throws Exception {
// A reasonable case where user1 is signed by user2 and later switches account and the new
// account gets signed by user2. This is not allowed.
KeyPair peer1KeyPair = Sig.generateKeyPair();
KeyPair peer2KeyPair = Sig.generateKeyPair();
byte[] user1PubKey = Sig.getPublicKeyBytes(peer1KeyPair.getPublic());
byte[] user2PubKey = Sig.getPublicKeyBytes(peer2KeyPair.getPublic());
signature2 = Sig.sign(peer1KeyPair.getPrivate(), Utilities.encodeToHex(account2DataHash).getBytes(Charsets.UTF_8));
signature3 = Sig.sign(peer2KeyPair.getPrivate(), Utilities.encodeToHex(account3DataHash).getBytes(Charsets.UTF_8));
SignedWitness sw1 = new SignedWitness(ARBITRATOR, account1DataHash, signature1, signer1PubKey, user1PubKey, date1, tradeAmount1);
SignedWitness sw2 = new SignedWitness(TRADE, account2DataHash, signature2, user1PubKey, user2PubKey, date2, tradeAmount2);
SignedWitness sw3 = new SignedWitness(TRADE, account3DataHash, signature3, user2PubKey, user1PubKey, date3, tradeAmount3);
signedWitnessService.addToMap(sw1);
signedWitnessService.addToMap(sw2);
signedWitnessService.addToMap(sw3);
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
}
@Test
public void testIsValidAccountAgeWitnessDateTooSoonProblem() {
date3 = getTodayMinusNDays(SIGN_AGE_2 - 1);
SignedWitness sw1 = new SignedWitness(ARBITRATOR, account1DataHash, signature1, signer1PubKey, witnessOwner1PubKey, date1, tradeAmount1);
SignedWitness sw2 = new SignedWitness(TRADE, account2DataHash, signature2, signer2PubKey, witnessOwner2PubKey, date2, tradeAmount2);
SignedWitness sw3 = new SignedWitness(TRADE, account3DataHash, signature3, signer3PubKey, witnessOwner3PubKey, date3, tradeAmount3);
signedWitnessService.addToMap(sw1);
signedWitnessService.addToMap(sw2);
signedWitnessService.addToMap(sw3);
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
}
@Test
public void testIsValidAccountAgeWitnessDateTooLateProblem() {
date3 = getTodayMinusNDays(3);
SignedWitness sw1 = new SignedWitness(ARBITRATOR, account1DataHash, signature1, signer1PubKey, witnessOwner1PubKey, date1, tradeAmount1);
SignedWitness sw2 = new SignedWitness(TRADE, account2DataHash, signature2, signer2PubKey, witnessOwner2PubKey, date2, tradeAmount2);
SignedWitness sw3 = new SignedWitness(TRADE, account3DataHash, signature3, signer3PubKey, witnessOwner3PubKey, date3, tradeAmount3);
signedWitnessService.addToMap(sw1);
signedWitnessService.addToMap(sw2);
signedWitnessService.addToMap(sw3);
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
}
@Test
public void testIsValidAccountAgeWitnessEndlessLoop() throws Exception {
byte[] account1DataHash = org.bitcoinj.core.Utils.sha256hash160(new byte[]{1});
byte[] account2DataHash = org.bitcoinj.core.Utils.sha256hash160(new byte[]{2});
byte[] account3DataHash = org.bitcoinj.core.Utils.sha256hash160(new byte[]{3});
long account1CreationTime = getTodayMinusNDays(SIGN_AGE_1 + 1);
long account2CreationTime = getTodayMinusNDays(SIGN_AGE_2 + 1);
long account3CreationTime = getTodayMinusNDays(SIGN_AGE_3 + 1);
AccountAgeWitness aew1 = new AccountAgeWitness(account1DataHash, account1CreationTime);
AccountAgeWitness aew2 = new AccountAgeWitness(account2DataHash, account2CreationTime);
AccountAgeWitness aew3 = new AccountAgeWitness(account3DataHash, account3CreationTime);
KeyPair peer1KeyPair = Sig.generateKeyPair();
KeyPair peer2KeyPair = Sig.generateKeyPair();
KeyPair peer3KeyPair = Sig.generateKeyPair();
String account1DataHashAsHexString = Utilities.encodeToHex(account1DataHash);
String account2DataHashAsHexString = Utilities.encodeToHex(account2DataHash);
String account3DataHashAsHexString = Utilities.encodeToHex(account3DataHash);
byte[] signature1 = Sig.sign(peer3KeyPair.getPrivate(), account1DataHashAsHexString.getBytes(Charsets.UTF_8));
byte[] signature2 = Sig.sign(peer1KeyPair.getPrivate(), account2DataHashAsHexString.getBytes(Charsets.UTF_8));
byte[] signature3 = Sig.sign(peer2KeyPair.getPrivate(), account3DataHashAsHexString.getBytes(Charsets.UTF_8));
byte[] signer1PubKey = Sig.getPublicKeyBytes(peer3KeyPair.getPublic());
byte[] signer2PubKey = Sig.getPublicKeyBytes(peer1KeyPair.getPublic());
byte[] signer3PubKey = Sig.getPublicKeyBytes(peer2KeyPair.getPublic());
byte[] witnessOwner1PubKey = Sig.getPublicKeyBytes(peer1KeyPair.getPublic());
byte[] witnessOwner2PubKey = Sig.getPublicKeyBytes(peer2KeyPair.getPublic());
byte[] witnessOwner3PubKey = Sig.getPublicKeyBytes(peer3KeyPair.getPublic());
long date1 = getTodayMinusNDays(SIGN_AGE_1);
long date2 = getTodayMinusNDays(SIGN_AGE_2);
long date3 = getTodayMinusNDays(SIGN_AGE_3);
long tradeAmount1 = 1000;
long tradeAmount2 = 1001;
long tradeAmount3 = 1001;
SignedWitness sw1 = new SignedWitness(TRADE, account1DataHash, signature1, signer1PubKey, witnessOwner1PubKey, date1, tradeAmount1);
SignedWitness sw2 = new SignedWitness(TRADE, account2DataHash, signature2, signer2PubKey, witnessOwner2PubKey, date2, tradeAmount2);
SignedWitness sw3 = new SignedWitness(TRADE, account3DataHash, signature3, signer3PubKey, witnessOwner3PubKey, date3, tradeAmount3);
signedWitnessService.addToMap(sw1);
signedWitnessService.addToMap(sw2);
signedWitnessService.addToMap(sw3);
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
}
@Test
public void testIsValidAccountAgeWitnessLongLoop() throws Exception {
AccountAgeWitness aew = null;
KeyPair signerKeyPair;
KeyPair signedKeyPair = Sig.generateKeyPair();
int iterations = 1002;
for (int i = 0; i < iterations; i++) {
byte[] accountDataHash = org.bitcoinj.core.Utils.sha256hash160(String.valueOf(i).getBytes(Charsets.UTF_8));
long accountCreationTime = getTodayMinusNDays((iterations - i) * (SignedWitnessService.SIGNER_AGE_DAYS + 1));
aew = new AccountAgeWitness(accountDataHash, accountCreationTime);
String accountDataHashAsHexString = Utilities.encodeToHex(accountDataHash);
byte[] signature;
byte[] signerPubKey;
if (i == 0) {
// use arbitrator key
ECKey arbitratorKey = new ECKey();
signedKeyPair = Sig.generateKeyPair();
String signature1String = arbitratorKey.signMessage(accountDataHashAsHexString);
signature = signature1String.getBytes(Charsets.UTF_8);
signerPubKey = arbitratorKey.getPubKey();
} else {
signerKeyPair = signedKeyPair;
signedKeyPair = Sig.generateKeyPair();
signature = Sig.sign(signedKeyPair.getPrivate(), accountDataHashAsHexString.getBytes(Charsets.UTF_8));
signerPubKey = Sig.getPublicKeyBytes(signerKeyPair.getPublic());
}
byte[] witnessOwnerPubKey = Sig.getPublicKeyBytes(signedKeyPair.getPublic());
long date = getTodayMinusNDays((iterations - i) * (SignedWitnessService.SIGNER_AGE_DAYS + 1));
SignedWitness sw = new SignedWitness(i == 0 ? ARBITRATOR : TRADE, accountDataHash, signature, signerPubKey, witnessOwnerPubKey, date, tradeAmount1);
signedWitnessService.addToMap(sw);
}
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew));
}
private long getTodayMinusNDays(long days) {
return Instant.ofEpochMilli(new Date().getTime()).minus(days, ChronoUnit.DAYS).toEpochMilli();
}
@Test
public void testSignAccountAgeWitness_withTooLowTradeAmount() throws CryptoException {
long accountCreationTime = getTodayMinusNDays(SIGN_AGE_1 + 1);
KeyPair peerKeyPair = Sig.generateKeyPair();
KeyPair signerKeyPair = Sig.generateKeyPair();
when(keyRing.getSignatureKeyPair()).thenReturn(signerKeyPair);
AccountAgeWitness accountAgeWitness = new AccountAgeWitness(account1DataHash, accountCreationTime);
signedWitnessService.signAndPublishAccountAgeWitness(Coin.ZERO, accountAgeWitness, peerKeyPair.getPublic());
verify(p2pService, never()).addPersistableNetworkPayload(any(PersistableNetworkPayload.class), anyBoolean());
}
@Test
public void testSignAccountAgeWitness_withSufficientTradeAmount() throws CryptoException {
long accountCreationTime = getTodayMinusNDays(SIGN_AGE_1 + 1);
KeyPair peerKeyPair = Sig.generateKeyPair();
KeyPair signerKeyPair = Sig.generateKeyPair();
when(keyRing.getSignatureKeyPair()).thenReturn(signerKeyPair);
AccountAgeWitness accountAgeWitness = new AccountAgeWitness(account1DataHash, accountCreationTime);
signedWitnessService.signAndPublishAccountAgeWitness(SignedWitnessService.MINIMUM_TRADE_AMOUNT_FOR_SIGNING, accountAgeWitness, peerKeyPair.getPublic());
verify(p2pService, times(1)).addPersistableNetworkPayload(any(PersistableNetworkPayload.class), anyBoolean());
}
/* Signed witness tree
Each edge in the graph represents one signature
Arbitrator
|
sw1
|
sw2
|
sw3
*/
@Test
public void testBanFilterSingleTree() {
SignedWitness sw1 = new SignedWitness(ARBITRATOR, account1DataHash, signature1, signer1PubKey, witnessOwner1PubKey, date1, tradeAmount1);
SignedWitness sw2 = new SignedWitness(TRADE, account2DataHash, signature2, signer2PubKey, witnessOwner2PubKey, date2, tradeAmount2);
SignedWitness sw3 = new SignedWitness(TRADE, account3DataHash, signature3, signer3PubKey, witnessOwner3PubKey, date3, tradeAmount3);
signedWitnessService.addToMap(sw1);
signedWitnessService.addToMap(sw2);
signedWitnessService.addToMap(sw3);
// Second account is banned, first account is still a signer but the other two are no longer signers
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(true);
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
// First account is banned, no accounts in the tree below it are signers
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(true);
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(false);
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
}
/* Signed witness trees
Each edge in the graph represents one signature
Arbitrator
| |
sw1 sw2
|
sw3
*/
@Test
public void testBanFilterTwoTrees() {
// Signer 2 is signed by arbitrator
signer2PubKey = arbitrator1Key.getPubKey();
signature2 = arbitrator1Key.signMessage(Utilities.encodeToHex(account2DataHash)).getBytes(Charsets.UTF_8);
SignedWitness sw1 = new SignedWitness(ARBITRATOR, account1DataHash, signature1, signer1PubKey, witnessOwner1PubKey, date1, tradeAmount1);
SignedWitness sw2 = new SignedWitness(ARBITRATOR, account2DataHash, signature2, signer2PubKey, witnessOwner2PubKey, date2, tradeAmount2);
SignedWitness sw3 = new SignedWitness(TRADE, account3DataHash, signature3, signer3PubKey, witnessOwner3PubKey, date3, tradeAmount3);
signedWitnessService.addToMap(sw1);
signedWitnessService.addToMap(sw2);
signedWitnessService.addToMap(sw3);
// Only second account is banned, first account is still a signer but the other two are no longer signers
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(true);
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
// Only first account is banned, account2 and account3 are still signers
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(true);
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(false);
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew3));
}
/* Signed witness tree
Each edge in the graph represents one signature
Arbitrator
| |
sw1 sw2
\ /
sw3
*/
@Test
public void testBanFilterJoinedTrees() throws Exception {
// Signer 2 is signed by arbitrator
signer2PubKey = arbitrator1Key.getPubKey();
signature2 = arbitrator1Key.signMessage(Utilities.encodeToHex(account2DataHash)).getBytes(Charsets.UTF_8);
// Peer1 owns both account1 and account2
// witnessOwner2PubKey = witnessOwner1PubKey;
// peer2KeyPair = peer1KeyPair;
// signature3 = Sig.sign(peer2KeyPair.getPrivate(), Utilities.encodeToHex(account3DataHash).getBytes(Charsets.UTF_8));
// sw1 also signs sw3 (not supported yet but a possible addition for a more robust system)
var signature3p = Sig.sign(peer1KeyPair.getPrivate(), Utilities.encodeToHex(account3DataHash).getBytes(Charsets.UTF_8));
var signer3pPubKey = witnessOwner1PubKey;
var date3p = date3;
var tradeAmount3p = tradeAmount3;
SignedWitness sw1 = new SignedWitness(ARBITRATOR, account1DataHash, signature1, signer1PubKey, witnessOwner1PubKey, date1, tradeAmount1);
SignedWitness sw2 = new SignedWitness(ARBITRATOR, account2DataHash, signature2, signer2PubKey, witnessOwner2PubKey, date2, tradeAmount2);
SignedWitness sw3 = new SignedWitness(TRADE, account3DataHash, signature3, signer3PubKey, witnessOwner3PubKey, date3, tradeAmount3);
SignedWitness sw3p = new SignedWitness(TRADE, account3DataHash, signature3p, signer3pPubKey, witnessOwner3PubKey, date3p, tradeAmount3p);
signedWitnessService.addToMap(sw1);
signedWitnessService.addToMap(sw2);
signedWitnessService.addToMap(sw3);
signedWitnessService.addToMap(sw3p);
// First account is banned, the other two are still signers
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(true);
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew3));
// Second account is banned, the other two are still signers
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(false);
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(true);
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew3));
// First and second account is banned, the third is no longer a signer
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(true);
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(true);
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
}
}

View file

@ -0,0 +1,62 @@
package bisq.core.account.sign;
import bisq.common.crypto.Sig;
import bisq.common.util.Utilities;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Utils;
import com.google.common.base.Charsets;
import java.time.Instant;
import org.junit.Before;
import org.junit.Test;
import static bisq.core.account.sign.SignedWitness.VerificationMethod.ARBITRATOR;
import static bisq.core.account.sign.SignedWitness.VerificationMethod.TRADE;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class SignedWitnessTest {
private ECKey arbitrator1Key;
private byte[] witnessOwner1PubKey;
private byte[] witnessHash;
private byte[] witnessHashSignature;
@Before
public void setUp() {
arbitrator1Key = new ECKey();
witnessOwner1PubKey = Sig.getPublicKeyBytes(Sig.generateKeyPair().getPublic());
witnessHash = Utils.sha256hash160(new byte[]{1});
witnessHashSignature = arbitrator1Key.signMessage(Utilities.encodeToHex(witnessHash)).getBytes(Charsets.UTF_8);
}
@Test
public void testProtoRoundTrip() {
SignedWitness signedWitness = new SignedWitness(ARBITRATOR, witnessHash, witnessHashSignature, arbitrator1Key.getPubKey(), witnessOwner1PubKey, Instant.now().getEpochSecond(), 100);
assertEquals(signedWitness, SignedWitness.fromProto(signedWitness.toProtoMessage().getSignedWitness()));
}
@Test
public void isImmutable() {
byte[] signerPubkey = arbitrator1Key.getPubKey();
SignedWitness signedWitness = new SignedWitness(TRADE, witnessHash, witnessHashSignature, signerPubkey, witnessOwner1PubKey, Instant.now().getEpochSecond(), 100);
byte[] originalWitnessHash = signedWitness.getAccountAgeWitnessHash().clone();
witnessHash[0] += 1;
assertArrayEquals(originalWitnessHash, signedWitness.getAccountAgeWitnessHash());
byte[] originalWitnessHashSignature = signedWitness.getSignature().clone();
witnessHashSignature[0] += 1;
assertArrayEquals(originalWitnessHashSignature, signedWitness.getSignature());
byte[] originalSignerPubkey = signedWitness.getSignerPubKey().clone();
signerPubkey[0] += 1;
assertArrayEquals(originalSignerPubkey, signedWitness.getSignerPubKey());
byte[] originalwitnessOwner1PubKey = signedWitness.getWitnessOwnerPubKey().clone();
witnessOwner1PubKey[0] += 1;
assertArrayEquals(originalwitnessOwner1PubKey, signedWitness.getWitnessOwnerPubKey());
}
}

View file

@ -0,0 +1,361 @@
/*
* 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.account.witness;
import bisq.core.account.sign.SignedWitness;
import bisq.core.account.sign.SignedWitnessService;
import bisq.core.filter.FilterManager;
import bisq.core.locale.CountryUtil;
import bisq.core.offer.OfferPayload;
import bisq.core.payment.ChargeBackRisk;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.payment.payload.SepaAccountPayload;
import bisq.core.support.SupportType;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.DisputeResult;
import bisq.core.support.dispute.arbitration.TraderDataItem;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.trade.Contract;
import bisq.network.p2p.P2PService;
import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService;
import bisq.common.crypto.CryptoException;
import bisq.common.crypto.Hash;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.KeyStorage;
import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.Sig;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import java.security.KeyPair;
import java.security.PublicKey;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import static bisq.core.payment.payload.PaymentMethod.getPaymentMethodById;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
// Restricted default Java security policy on Travis does not allow long keys, so test fails.
// Using Utilities.removeCryptographyRestrictions(); did not work.
//@Ignore
public class AccountAgeWitnessServiceTest {
private PublicKey publicKey;
private KeyPair keypair;
private SignedWitnessService signedWitnessService;
private AccountAgeWitnessService service;
private ChargeBackRisk chargeBackRisk;
private FilterManager filterManager;
private File dir1;
private File dir2;
private File dir3;
@Before
public void setup() throws IOException {
KeyRing keyRing = mock(KeyRing.class);
setupService(keyRing);
keypair = Sig.generateKeyPair();
publicKey = keypair.getPublic();
// Setup temp storage dir
dir1 = makeDir("temp_tests1");
dir2 = makeDir("temp_tests1");
dir3 = makeDir("temp_tests1");
}
private void setupService(KeyRing keyRing) {
chargeBackRisk = mock(ChargeBackRisk.class);
AppendOnlyDataStoreService dataStoreService = mock(AppendOnlyDataStoreService.class);
P2PService p2pService = mock(P2PService.class);
ArbitratorManager arbitratorManager = mock(ArbitratorManager.class);
when(arbitratorManager.isPublicKeyInList(any())).thenReturn(true);
AppendOnlyDataStoreService appendOnlyDataStoreService = mock(AppendOnlyDataStoreService.class);
filterManager = mock(FilterManager.class);
signedWitnessService = new SignedWitnessService(keyRing, p2pService, arbitratorManager, null, appendOnlyDataStoreService, null, filterManager);
service = new AccountAgeWitnessService(null, null, null, signedWitnessService, chargeBackRisk, null, dataStoreService, null, filterManager);
}
private File makeDir(String name) throws IOException {
var dir = File.createTempFile(name, "");
dir.delete();
dir.mkdir();
return dir;
}
@After
public void tearDown() {
// Do teardown stuff
}
@Ignore
@Test
public void testIsTradeDateAfterReleaseDate() {
Date ageWitnessReleaseDate = new GregorianCalendar(2017, Calendar.OCTOBER, 23).getTime();
Date tradeDate = new GregorianCalendar(2017, Calendar.NOVEMBER, 1).getTime();
assertTrue(service.isDateAfterReleaseDate(tradeDate.getTime(), ageWitnessReleaseDate, errorMessage -> {
}));
tradeDate = new GregorianCalendar(2017, Calendar.OCTOBER, 23).getTime();
assertTrue(service.isDateAfterReleaseDate(tradeDate.getTime(), ageWitnessReleaseDate, errorMessage -> {
}));
tradeDate = new GregorianCalendar(2017, Calendar.OCTOBER, 22, 0, 0, 1).getTime();
assertTrue(service.isDateAfterReleaseDate(tradeDate.getTime(), ageWitnessReleaseDate, errorMessage -> {
}));
tradeDate = new GregorianCalendar(2017, Calendar.OCTOBER, 22).getTime();
assertFalse(service.isDateAfterReleaseDate(tradeDate.getTime(), ageWitnessReleaseDate, errorMessage -> {
}));
tradeDate = new GregorianCalendar(2017, Calendar.OCTOBER, 21).getTime();
assertFalse(service.isDateAfterReleaseDate(tradeDate.getTime(), ageWitnessReleaseDate, errorMessage -> {
}));
}
@Ignore
@Test
public void testVerifySignatureOfNonce() throws CryptoException {
byte[] nonce = new byte[]{0x01};
byte[] signature = Sig.sign(keypair.getPrivate(), nonce);
assertTrue(service.verifySignature(publicKey, nonce, signature, errorMessage -> {
}));
assertFalse(service.verifySignature(publicKey, nonce, new byte[]{0x02}, errorMessage -> {
}));
assertFalse(service.verifySignature(publicKey, new byte[]{0x03}, signature, errorMessage -> {
}));
assertFalse(service.verifySignature(publicKey, new byte[]{0x02}, new byte[]{0x04}, errorMessage -> {
}));
}
@Test
public void testArbitratorSignWitness() {
KeyRing buyerKeyRing = new KeyRing(new KeyStorage(dir1));
KeyRing sellerKeyRing = new KeyRing(new KeyStorage(dir2));
// Setup dispute for arbitrator to sign both sides
List<Dispute> disputes = new ArrayList<>();
PubKeyRing buyerPubKeyRing = buyerKeyRing.getPubKeyRing();
PubKeyRing sellerPubKeyRing = sellerKeyRing.getPubKeyRing();
PaymentAccountPayload buyerPaymentAccountPayload = new SepaAccountPayload(PaymentMethod.SEPA_ID, "1", CountryUtil.getAllSepaCountries());
PaymentAccountPayload sellerPaymentAccountPayload = new SepaAccountPayload(PaymentMethod.SEPA_ID, "2", CountryUtil.getAllSepaCountries());
AccountAgeWitness buyerAccountAgeWitness = service.getNewWitness(buyerPaymentAccountPayload, buyerPubKeyRing);
service.addToMap(buyerAccountAgeWitness);
AccountAgeWitness sellerAccountAgeWitness = service.getNewWitness(sellerPaymentAccountPayload, sellerPubKeyRing);
service.addToMap(sellerAccountAgeWitness);
long now = new Date().getTime() + 1000;
Contract contract = mock(Contract.class);
disputes.add(new Dispute(new Date().getTime(),
"trade1",
0,
true,
true,
buyerPubKeyRing,
now - 1,
now - 1,
contract,
null,
null,
null,
null,
null,
"contractAsJson",
null,
null,
null,
true,
SupportType.ARBITRATION));
disputes.get(0).setIsClosed();
disputes.get(0).getDisputeResultProperty().set(new DisputeResult(
"trade1",
1,
DisputeResult.Winner.BUYER,
DisputeResult.Reason.OTHER.ordinal(),
true,
true,
true,
"summary",
null,
null,
100000,
0,
null,
now - 1,
false));
// Filtermanager says nothing is filtered
when(filterManager.isNodeAddressBanned(any())).thenReturn(false);
when(filterManager.isCurrencyBanned(any())).thenReturn(false);
when(filterManager.isPaymentMethodBanned(any())).thenReturn(false);
when(filterManager.arePeersPaymentAccountDataBanned(any())).thenReturn(false);
when(filterManager.isWitnessSignerPubKeyBanned(any())).thenReturn(false);
when(chargeBackRisk.hasChargebackRisk(any(), any())).thenReturn(true);
when(contract.getPaymentMethodId()).thenReturn(PaymentMethod.SEPA_ID);
when(contract.getTradeAmount()).thenReturn(Coin.parseCoin("0.01"));
when(contract.getBuyerPubKeyRing()).thenReturn(buyerPubKeyRing);
when(contract.getSellerPubKeyRing()).thenReturn(sellerPubKeyRing);
when(contract.getBuyerPaymentAccountPayload()).thenReturn(buyerPaymentAccountPayload);
when(contract.getSellerPaymentAccountPayload()).thenReturn(sellerPaymentAccountPayload);
when(contract.getOfferPayload()).thenReturn(mock(OfferPayload.class));
List<TraderDataItem> items = service.getTraderPaymentAccounts(now, getPaymentMethodById(PaymentMethod.SEPA_ID), disputes);
assertEquals(2, items.size());
// Setup a mocked arbitrator key
ECKey arbitratorKey = mock(ECKey.class);
when(arbitratorKey.signMessage(any())).thenReturn("1");
when(arbitratorKey.signMessage(any())).thenReturn("2");
when(arbitratorKey.getPubKey()).thenReturn("1".getBytes());
// Arbitrator signs both trader accounts
items.forEach(item -> service.arbitratorSignAccountAgeWitness(
item.getTradeAmount(),
item.getAccountAgeWitness(),
arbitratorKey,
item.getPeersPubKey()));
// Check that both accountAgeWitnesses are signed
SignedWitness foundBuyerSignedWitness = signedWitnessService.getSignedWitnessSetByOwnerPubKey(
buyerPubKeyRing.getSignaturePubKeyBytes()).stream()
.findFirst()
.orElse(null);
assert foundBuyerSignedWitness != null;
assertEquals(Utilities.bytesAsHexString(foundBuyerSignedWitness.getAccountAgeWitnessHash()),
Utilities.bytesAsHexString(buyerAccountAgeWitness.getHash()));
SignedWitness foundSellerSignedWitness = signedWitnessService.getSignedWitnessSetByOwnerPubKey(
sellerPubKeyRing.getSignaturePubKeyBytes()).stream()
.findFirst()
.orElse(null);
assert foundSellerSignedWitness != null;
assertEquals(Utilities.bytesAsHexString(foundSellerSignedWitness.getAccountAgeWitnessHash()),
Utilities.bytesAsHexString(sellerAccountAgeWitness.getHash()));
}
// Create a tree of signed witnesses Arb -(SWA)-> aew1 -(SW1)-> aew2 -(SW2)-> aew3
// Delete SWA signature, none of the account age witnesses are considered signed
// Sign a dummy AccountAgeWitness using the signerPubkey from SW1; aew2 and aew3 are not considered signed. The
// lost SignedWitness isn't possible to recover so aew1 is still not signed, but it's pubkey is a signer.
@Test
public void testArbitratorSignDummyWitness() throws CryptoException {
ECKey arbitratorKey = new ECKey();
// Init 2 user accounts
var user1KeyRing = new KeyRing(new KeyStorage(dir1));
var user2KeyRing = new KeyRing(new KeyStorage(dir2));
var user3KeyRing = new KeyRing(new KeyStorage(dir3));
var pubKeyRing1 = user1KeyRing.getPubKeyRing();
var pubKeyRing2 = user2KeyRing.getPubKeyRing();
var pubKeyRing3 = user3KeyRing.getPubKeyRing();
var account1 = new SepaAccountPayload(PaymentMethod.SEPA_ID, "1", CountryUtil.getAllSepaCountries());
var account2 = new SepaAccountPayload(PaymentMethod.SEPA_ID, "2", CountryUtil.getAllSepaCountries());
var account3 = new SepaAccountPayload(PaymentMethod.SEPA_ID, "3", CountryUtil.getAllSepaCountries());
var aew1 = service.getNewWitness(account1, pubKeyRing1);
var aew2 = service.getNewWitness(account2, pubKeyRing2);
var aew3 = service.getNewWitness(account3, pubKeyRing3);
// Backdate witness1 70 days
aew1 = new AccountAgeWitness(aew1.getHash(), new Date().getTime() - TimeUnit.DAYS.toMillis(70));
aew2 = new AccountAgeWitness(aew2.getHash(), new Date().getTime() - TimeUnit.DAYS.toMillis(35));
aew3 = new AccountAgeWitness(aew3.getHash(), new Date().getTime() - TimeUnit.DAYS.toMillis(1));
service.addToMap(aew1);
service.addToMap(aew2);
service.addToMap(aew3);
// Test as user1. It's still possible to sign as arbitrator since the ECKey is passed as an argument.
setupService(user1KeyRing);
// Arbitrator signs user1
service.arbitratorSignAccountAgeWitness(aew1, arbitratorKey, pubKeyRing1.getSignaturePubKeyBytes(),
aew1.getDate());
// user1 signs user2
signAccountAgeWitness(aew2, pubKeyRing2.getSignaturePubKey(), aew2.getDate(), user1KeyRing);
// user2 signs user3
signAccountAgeWitness(aew3, pubKeyRing3.getSignaturePubKey(), aew3.getDate(), user2KeyRing);
signedWitnessService.signAndPublishAccountAgeWitness(SignedWitnessService.MINIMUM_TRADE_AMOUNT_FOR_SIGNING, aew2,
pubKeyRing2.getSignaturePubKey());
assertTrue(service.accountIsSigner(aew1));
assertTrue(service.accountIsSigner(aew2));
assertFalse(service.accountIsSigner(aew3));
assertTrue(signedWitnessService.isSignedAccountAgeWitness(aew3));
// Remove SignedWitness signed by arbitrator
@SuppressWarnings("OptionalGetWithoutIsPresent")
var signedWitnessArb = signedWitnessService.getSignedWitnessMapValues().stream()
.filter(sw -> sw.getVerificationMethod() == SignedWitness.VerificationMethod.ARBITRATOR)
.findAny()
.get();
signedWitnessService.removeSignedWitness(signedWitnessArb);
assertEquals(signedWitnessService.getSignedWitnessMapValues().size(), 2);
// Check that no account age witness is a signer
assertFalse(service.accountIsSigner(aew1));
assertFalse(service.accountIsSigner(aew2));
assertFalse(service.accountIsSigner(aew3));
assertFalse(signedWitnessService.isSignedAccountAgeWitness(aew2));
// Sign dummy AccountAgeWitness using signer key from SW_1
assertEquals(signedWitnessService.getRootSignedWitnessSet(false).size(), 1);
// TODO: move this to accountagewitnessservice
@SuppressWarnings("OptionalGetWithoutIsPresent")
var orphanedSignedWitness = signedWitnessService.getRootSignedWitnessSet(false).stream().findAny().get();
var dummyAccountAgeWitnessHash = Hash.getRipemd160hash(orphanedSignedWitness.getSignerPubKey());
var dummyAEW = new AccountAgeWitness(dummyAccountAgeWitnessHash,
orphanedSignedWitness.getDate() -
(TimeUnit.DAYS.toMillis(SignedWitnessService.SIGNER_AGE_DAYS + 1)));
service.arbitratorSignAccountAgeWitness(
dummyAEW, arbitratorKey, orphanedSignedWitness.getSignerPubKey(), dummyAEW.getDate());
assertFalse(service.accountIsSigner(aew1));
assertTrue(service.accountIsSigner(aew2));
assertFalse(service.accountIsSigner(aew3));
assertTrue(signedWitnessService.isSignedAccountAgeWitness(aew2));
}
private void signAccountAgeWitness(AccountAgeWitness accountAgeWitness,
PublicKey witnessOwnerPubKey,
long time,
KeyRing signerKeyRing) throws CryptoException {
byte[] signature = Sig.sign(signerKeyRing.getSignatureKeyPair().getPrivate(), accountAgeWitness.getHash());
SignedWitness signedWitness = new SignedWitness(SignedWitness.VerificationMethod.TRADE,
accountAgeWitness.getHash(),
signature,
signerKeyRing.getSignatureKeyPair().getPublic().getEncoded(),
witnessOwnerPubKey.getEncoded(),
time,
SignedWitnessService.MINIMUM_TRADE_AMOUNT_FOR_SIGNING.value);
signedWitnessService.addToMap(signedWitness);
}
}

View file

@ -0,0 +1,142 @@
/*
* 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.app;
import bisq.common.config.BisqHelpFormatter;
import joptsimple.OptionParser;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
public class BisqHelpFormatterTest {
@Test
public void testHelpFormatter() throws IOException, URISyntaxException {
OptionParser parser = new OptionParser();
parser.formatHelpWith(new BisqHelpFormatter("Bisq Test", "bisq-test", "0.1.0"));
parser.accepts("name",
"The name of the Bisq node")
.withRequiredArg()
.ofType(String.class)
.defaultsTo("Bisq");
parser.accepts("another-option",
"This is a long description which will need to break over multiple linessssssssssss such " +
"that no line is longer than 80 characters in the help output.")
.withRequiredArg()
.ofType(String.class)
.defaultsTo("WAT");
parser.accepts("exactly-72-char-description",
"012345678911234567892123456789312345678941234567895123456789612345678971")
.withRequiredArg()
.ofType(String.class);
parser.accepts("exactly-72-char-description-with-spaces",
" 123456789 123456789 123456789 123456789 123456789 123456789 123456789 1")
.withRequiredArg()
.ofType(String.class);
parser.accepts("90-char-description-without-spaces",
"-123456789-223456789-323456789-423456789-523456789-623456789-723456789-823456789-923456789")
.withRequiredArg()
.ofType(String.class);
parser.accepts("90-char-description-with-space-at-char-80",
"-123456789-223456789-323456789-423456789-523456789-623456789-723456789-823456789 923456789")
.withRequiredArg()
.ofType(String.class);
parser.accepts("90-char-description-with-spaces-at-chars-5-and-80",
"-123 56789-223456789-323456789-423456789-523456789-623456789-723456789-823456789 923456789")
.withRequiredArg()
.ofType(String.class);
parser.accepts("90-char-description-with-space-at-char-73",
"-123456789-223456789-323456789-423456789-523456789-623456789-723456789-8 3456789-923456789")
.withRequiredArg()
.ofType(String.class);
parser.accepts("1-char-description-with-only-a-space", " ")
.withRequiredArg()
.ofType(String.class);
parser.accepts("empty-description", "")
.withRequiredArg()
.ofType(String.class);
parser.accepts("no-description")
.withRequiredArg()
.ofType(String.class);
parser.accepts("no-arg", "Some description");
parser.accepts("optional-arg",
"Option description")
.withOptionalArg();
parser.accepts("with-default-value",
"Some option with a default value")
.withRequiredArg()
.ofType(String.class)
.defaultsTo("Wat");
parser.accepts("data-dir",
"Application data directory")
.withRequiredArg()
.ofType(File.class)
.defaultsTo(new File("/Users/cbeams/Library/Application Support/Bisq"));
parser.accepts("enum-opt",
"Some option that accepts an enum value as an argument")
.withRequiredArg()
.ofType(AnEnum.class)
.defaultsTo(AnEnum.foo);
ByteArrayOutputStream actual = new ByteArrayOutputStream();
String expected = new String(Files.readAllBytes(Paths.get(getClass().getResource("cli-output.txt").toURI())));
if (System.getProperty("os.name").startsWith("Windows")) {
// Load the expected content from a different file for Windows due to different path separator
// And normalize line endings to LF in case the file has CRLF line endings
expected = new String(Files.readAllBytes(Paths.get(getClass().getResource("cli-output_windows.txt").toURI())))
.replaceAll("\\r\\n?", "\n");
}
parser.printHelpOn(new PrintStream(actual));
assertThat(actual.toString(), equalTo(expected));
}
enum AnEnum {foo, bar, baz}
}

View file

@ -0,0 +1,118 @@
/*
* 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.arbitration;
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorService;
import bisq.core.user.User;
import bisq.network.p2p.NodeAddress;
import java.util.ArrayList;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
public class ArbitratorManagerTest {
@Test
public void testIsArbitratorAvailableForLanguage() {
User user = mock(User.class);
ArbitratorService arbitratorService = mock(ArbitratorService.class);
ArbitratorManager manager = new ArbitratorManager(null, arbitratorService, user, null, false);
ArrayList<String> languagesOne = new ArrayList<String>() {{
add("en");
add("de");
}};
ArrayList<String> languagesTwo = new ArrayList<String>() {{
add("en");
add("es");
}};
Arbitrator one = new Arbitrator(new NodeAddress("arbitrator:1"), null, null, null,
languagesOne, 0L, null, "", null,
null, null);
Arbitrator two = new Arbitrator(new NodeAddress("arbitrator:2"), null, null, null,
languagesTwo, 0L, null, "", null,
null, null);
manager.addDisputeAgent(one, () -> {
}, errorMessage -> {
});
manager.addDisputeAgent(two, () -> {
}, errorMessage -> {
});
assertTrue(manager.isAgentAvailableForLanguage("en"));
assertFalse(manager.isAgentAvailableForLanguage("th"));
}
@Test
public void testGetArbitratorLanguages() {
User user = mock(User.class);
ArbitratorService arbitratorService = mock(ArbitratorService.class);
ArbitratorManager manager = new ArbitratorManager(null, arbitratorService, user, null, false);
ArrayList<String> languagesOne = new ArrayList<String>() {{
add("en");
add("de");
}};
ArrayList<String> languagesTwo = new ArrayList<String>() {{
add("en");
add("es");
}};
Arbitrator one = new Arbitrator(new NodeAddress("arbitrator:1"), null, null, null,
languagesOne, 0L, null, "", null,
null, null);
Arbitrator two = new Arbitrator(new NodeAddress("arbitrator:2"), null, null, null,
languagesTwo, 0L, null, "", null,
null, null);
ArrayList<NodeAddress> nodeAddresses = new ArrayList<NodeAddress>() {{
add(two.getNodeAddress());
}};
manager.addDisputeAgent(one, () -> {
}, errorMessage -> {
});
manager.addDisputeAgent(two, () -> {
}, errorMessage -> {
});
assertThat(manager.getDisputeAgentLanguages(nodeAddresses), containsInAnyOrder("en", "es"));
assertThat(manager.getDisputeAgentLanguages(nodeAddresses), not(containsInAnyOrder("de")));
}
}

View file

@ -0,0 +1,60 @@
/*
* 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.arbitration;
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.network.p2p.NodeAddress;
import bisq.common.crypto.PubKeyRing;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.RandomUtils;
import java.util.Date;
import org.junit.Ignore;
@SuppressWarnings({"SameParameterValue", "UnusedAssignment"})
public class ArbitratorTest {
@Ignore("TODO InvalidKeySpecException at bisq.common.crypto.Sig.getPublicKeyFromBytes(Sig.java:135)")
public void testRoundtrip() {
Arbitrator arbitrator = getArbitratorMock();
Arbitrator newVo = Arbitrator.fromProto(arbitrator.toProtoMessage().getArbitrator());
}
public static Arbitrator getArbitratorMock() {
return new Arbitrator(new NodeAddress("host", 1000),
getBytes(100),
"btcaddress",
new PubKeyRing(getBytes(100), getBytes(100)),
Lists.newArrayList(),
new Date().getTime(),
getBytes(100),
"registrationSignature",
null, null, null);
}
public static byte[] getBytes(@SuppressWarnings("SameParameterValue") int count) {
return RandomUtils.nextBytes(count);
}
}

View file

@ -0,0 +1,56 @@
/*
* 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.arbitration;
import bisq.core.support.dispute.mediation.mediator.Mediator;
import bisq.network.p2p.NodeAddress;
import bisq.common.crypto.PubKeyRing;
import com.google.common.collect.Lists;
import java.util.Date;
import org.junit.Ignore;
import static bisq.core.arbitration.ArbitratorTest.getBytes;
public class MediatorTest {
@Ignore("TODO InvalidKeySpecException at bisq.common.crypto.Sig.getPublicKeyFromBytes(Sig.java:135)")
public void testRoundtrip() {
Mediator Mediator = getMediatorMock();
//noinspection AccessStaticViaInstance
Mediator.fromProto(Mediator.toProtoMessage().getMediator());
}
public static Mediator getMediatorMock() {
return new Mediator(new NodeAddress("host", 1000),
new PubKeyRing(getBytes(100), getBytes(100)),
Lists.newArrayList(),
new Date().getTime(),
getBytes(100),
"registrationSignature",
"email",
"info",
null);
}
}

View file

@ -0,0 +1,67 @@
package bisq.core.arbitration;
import bisq.core.account.witness.AccountAgeWitness;
import bisq.core.support.dispute.arbitration.TraderDataItem;
import bisq.core.payment.payload.PaymentAccountPayload;
import org.bitcoinj.core.Coin;
import java.security.PublicKey;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.Mockito.mock;
/*
* 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/>.
*/
public class TraderDataItemTest {
private TraderDataItem traderDataItem1;
private TraderDataItem traderDataItem2;
private TraderDataItem traderDataItem3;
private AccountAgeWitness accountAgeWitness1;
private AccountAgeWitness accountAgeWitness2;
private byte[] hash1 = "1".getBytes();
private byte[] hash2 = "2".getBytes();
@Before
public void setup() {
accountAgeWitness1 = new AccountAgeWitness(hash1, 123);
accountAgeWitness2 = new AccountAgeWitness(hash2, 124);
traderDataItem1 = new TraderDataItem(mock(PaymentAccountPayload.class), accountAgeWitness1, Coin.valueOf(546),
mock(PublicKey.class));
traderDataItem2 = new TraderDataItem(mock(PaymentAccountPayload.class), accountAgeWitness1, Coin.valueOf(547),
mock(PublicKey.class));
traderDataItem3 = new TraderDataItem(mock(PaymentAccountPayload.class), accountAgeWitness2, Coin.valueOf(548),
mock(PublicKey.class));
}
@Test
public void testEquals() {
assertEquals(traderDataItem1, traderDataItem2);
assertNotEquals(traderDataItem1, traderDataItem3);
assertNotEquals(traderDataItem2, traderDataItem3);
}
@Test
public void testHashCode() {
assertEquals(traderDataItem1.hashCode(), traderDataItem2.hashCode());
assertNotEquals(traderDataItem1.hashCode(), traderDataItem3.hashCode());
}
}

View file

@ -0,0 +1,141 @@
/*
* 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.btc;
import bisq.core.btc.wallet.BtcWalletService;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import java.util.List;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class TxFeeEstimationServiceTest {
@Test
public void testGetEstimatedTxVsize_withDefaultTxVsize() throws InsufficientMoneyException {
List<Coin> outputValues = List.of(Coin.valueOf(2000), Coin.valueOf(3000));
int initialEstimatedTxVsize;
Coin txFeePerVbyte;
BtcWalletService btcWalletService = mock(BtcWalletService.class);
int result;
int realTxVsize;
Coin txFee;
initialEstimatedTxVsize = 175;
txFeePerVbyte = Coin.valueOf(10);
realTxVsize = 175;
txFee = txFeePerVbyte.multiply(initialEstimatedTxVsize);
when(btcWalletService.getEstimatedFeeTxVsize(outputValues, txFee)).thenReturn(realTxVsize);
result = TxFeeEstimationService.getEstimatedTxVsize(outputValues, initialEstimatedTxVsize, txFeePerVbyte, btcWalletService);
assertEquals(175, result);
}
// FIXME @Bernard could you have a look?
@Test
@Ignore
public void testGetEstimatedTxVsize_withLargeTx() throws InsufficientMoneyException {
List<Coin> outputValues = List.of(Coin.valueOf(2000), Coin.valueOf(3000));
int initialEstimatedTxVsize;
Coin txFeePerVbyte;
BtcWalletService btcWalletService = mock(BtcWalletService.class);
int result;
int realTxVsize;
Coin txFee;
initialEstimatedTxVsize = 175;
txFeePerVbyte = Coin.valueOf(10);
realTxVsize = 1750;
txFee = txFeePerVbyte.multiply(initialEstimatedTxVsize);
when(btcWalletService.getEstimatedFeeTxVsize(outputValues, txFee)).thenReturn(realTxVsize);
// repeated calls to getEstimatedFeeTxVsize do not work (returns 0 at second call in loop which cause test to fail)
result = TxFeeEstimationService.getEstimatedTxVsize(outputValues, initialEstimatedTxVsize, txFeePerVbyte, btcWalletService);
assertEquals(1750, result);
}
// FIXME @Bernard could you have a look?
@Test
@Ignore
public void testGetEstimatedTxVsize_withSmallTx() throws InsufficientMoneyException {
List<Coin> outputValues = List.of(Coin.valueOf(2000), Coin.valueOf(3000));
int initialEstimatedTxVsize;
Coin txFeePerVbyte;
BtcWalletService btcWalletService = mock(BtcWalletService.class);
int result;
int realTxVsize;
Coin txFee;
initialEstimatedTxVsize = 1750;
txFeePerVbyte = Coin.valueOf(10);
realTxVsize = 175;
txFee = txFeePerVbyte.multiply(initialEstimatedTxVsize);
when(btcWalletService.getEstimatedFeeTxVsize(outputValues, txFee)).thenReturn(realTxVsize);
result = TxFeeEstimationService.getEstimatedTxVsize(outputValues, initialEstimatedTxVsize, txFeePerVbyte, btcWalletService);
assertEquals(175, result);
}
@Test
public void testIsInTolerance() {
int estimatedSize;
int txVsize;
double tolerance;
boolean result;
estimatedSize = 100;
txVsize = 100;
tolerance = 0.0001;
result = TxFeeEstimationService.isInTolerance(estimatedSize, txVsize, tolerance);
assertTrue(result);
estimatedSize = 100;
txVsize = 200;
tolerance = 0.2;
result = TxFeeEstimationService.isInTolerance(estimatedSize, txVsize, tolerance);
assertFalse(result);
estimatedSize = 120;
txVsize = 100;
tolerance = 0.2;
result = TxFeeEstimationService.isInTolerance(estimatedSize, txVsize, tolerance);
assertTrue(result);
estimatedSize = 200;
txVsize = 100;
tolerance = 1;
result = TxFeeEstimationService.isInTolerance(estimatedSize, txVsize, tolerance);
assertTrue(result);
estimatedSize = 201;
txVsize = 100;
tolerance = 1;
result = TxFeeEstimationService.isInTolerance(estimatedSize, txVsize, tolerance);
assertFalse(result);
}
}

View file

@ -0,0 +1,78 @@
/*
* 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.btc.nodes;
import bisq.core.btc.setup.WalletConfig;
import bisq.network.Socks5MultiDiscovery;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.PeerAddress;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
public class BtcNetworkConfigTest {
private static final int MODE = 0;
private WalletConfig delegate;
@Before
public void setUp() {
delegate = mock(WalletConfig.class);
}
@Test
public void testProposePeersWhenProxyPresentAndNoPeers() {
BtcNetworkConfig config = new BtcNetworkConfig(delegate, mock(NetworkParameters.class), MODE,
mock(Socks5Proxy.class));
config.proposePeers(Collections.emptyList());
verify(delegate, never()).setPeerNodes(any());
verify(delegate).setDiscovery(any(Socks5MultiDiscovery.class));
}
@Test
public void testProposePeersWhenProxyNotPresentAndNoPeers() {
BtcNetworkConfig config = new BtcNetworkConfig(delegate, mock(NetworkParameters.class), MODE,
null);
config.proposePeers(Collections.emptyList());
verify(delegate, never()).setDiscovery(any(Socks5MultiDiscovery.class));
verify(delegate, never()).setPeerNodes(any());
}
@Test
public void testProposePeersWhenPeersPresent() {
BtcNetworkConfig config = new BtcNetworkConfig(delegate, mock(NetworkParameters.class), MODE,
null);
config.proposePeers(Collections.singletonList(mock(PeerAddress.class)));
verify(delegate, never()).setDiscovery(any(Socks5MultiDiscovery.class));
verify(delegate).setPeerNodes(any());
}
}

View file

@ -0,0 +1,83 @@
/*
* 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.btc.nodes;
import bisq.core.btc.nodes.BtcNodeConverter.Facade;
import bisq.core.btc.nodes.BtcNodes.BtcNode;
import bisq.network.DnsLookupException;
import org.bitcoinj.core.PeerAddress;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import java.net.InetAddress;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class BtcNodeConverterTest {
@Test
public void testConvertOnionHost() {
BtcNode node = mock(BtcNode.class);
when(node.getOnionAddress()).thenReturn("aaa.onion");
//InetAddress inetAddress = mock(InetAddress.class);
Facade facade = mock(Facade.class);
//when(facade.onionHostToInetAddress(any())).thenReturn(inetAddress);
PeerAddress peerAddress = new BtcNodeConverter(facade).convertOnionHost(node);
// noinspection ConstantConditions
assertEquals(node.getOnionAddress(), peerAddress.getHostname());
}
@Test
public void testConvertClearNode() {
final String ip = "192.168.0.1";
BtcNode node = mock(BtcNode.class);
when(node.getHostNameOrAddress()).thenReturn(ip);
PeerAddress peerAddress = new BtcNodeConverter().convertClearNode(node);
// noinspection ConstantConditions
InetAddress inetAddress = peerAddress.getAddr();
assertEquals(ip, inetAddress.getHostAddress());
}
@Test
public void testConvertWithTor() throws DnsLookupException {
InetAddress expected = mock(InetAddress.class);
Facade facade = mock(Facade.class);
when(facade.torLookup(any(), anyString())).thenReturn(expected);
BtcNode node = mock(BtcNode.class);
when(node.getHostNameOrAddress()).thenReturn("aaa.onion");
PeerAddress peerAddress = new BtcNodeConverter(facade).convertWithTor(node, mock(Socks5Proxy.class));
// noinspection ConstantConditions
assertEquals(expected, peerAddress.getAddr());
}
}

View file

@ -0,0 +1,107 @@
/*
* 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.btc.nodes;
import bisq.core.btc.nodes.BtcNodes.BtcNode;
import org.bitcoinj.core.PeerAddress;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import com.google.common.collect.Lists;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class BtcNodesRepositoryTest {
@Test
public void testGetPeerAddressesWhenClearNodes() {
BtcNode node = mock(BtcNode.class);
when(node.hasClearNetAddress()).thenReturn(true);
BtcNodeConverter converter = mock(BtcNodeConverter.class, RETURNS_DEEP_STUBS);
BtcNodesRepository repository = new BtcNodesRepository(converter,
Collections.singletonList(node));
List<PeerAddress> peers = repository.getPeerAddresses(null, false);
assertFalse(peers.isEmpty());
}
@Test
public void testGetPeerAddressesWhenConverterReturnsNull() {
BtcNodeConverter converter = mock(BtcNodeConverter.class);
when(converter.convertClearNode(any())).thenReturn(null);
BtcNode node = mock(BtcNode.class);
when(node.hasClearNetAddress()).thenReturn(true);
BtcNodesRepository repository = new BtcNodesRepository(converter,
Collections.singletonList(node));
List<PeerAddress> peers = repository.getPeerAddresses(null, false);
verify(converter).convertClearNode(any());
assertTrue(peers.isEmpty());
}
@Test
public void testGetPeerAddressesWhenProxyAndClearNodes() {
BtcNode node = mock(BtcNode.class);
when(node.hasClearNetAddress()).thenReturn(true);
BtcNode onionNode = mock(BtcNode.class);
when(node.hasOnionAddress()).thenReturn(true);
BtcNodeConverter converter = mock(BtcNodeConverter.class, RETURNS_DEEP_STUBS);
BtcNodesRepository repository = new BtcNodesRepository(converter,
Lists.newArrayList(node, onionNode));
List<PeerAddress> peers = repository.getPeerAddresses(mock(Socks5Proxy.class), true);
assertEquals(2, peers.size());
}
@Test
public void testGetPeerAddressesWhenOnionNodesOnly() {
BtcNode node = mock(BtcNode.class);
when(node.hasClearNetAddress()).thenReturn(true);
BtcNode onionNode = mock(BtcNode.class);
when(node.hasOnionAddress()).thenReturn(true);
BtcNodeConverter converter = mock(BtcNodeConverter.class, RETURNS_DEEP_STUBS);
BtcNodesRepository repository = new BtcNodesRepository(converter,
Lists.newArrayList(node, onionNode));
List<PeerAddress> peers = repository.getPeerAddresses(mock(Socks5Proxy.class), false);
assertEquals(1, peers.size());
}
}

View file

@ -0,0 +1,57 @@
/*
* 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.btc.nodes;
import bisq.core.btc.nodes.BtcNodes.BtcNode;
import bisq.core.user.Preferences;
import java.util.List;
import org.junit.Test;
import static bisq.core.btc.nodes.BtcNodes.BitcoinNodesOption.CUSTOM;
import static bisq.core.btc.nodes.BtcNodes.BitcoinNodesOption.PUBLIC;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class BtcNodesSetupPreferencesTest {
@Test
public void testSelectPreferredNodesWhenPublicOption() {
Preferences delegate = mock(Preferences.class);
when(delegate.getBitcoinNodesOptionOrdinal()).thenReturn(PUBLIC.ordinal());
BtcNodesSetupPreferences preferences = new BtcNodesSetupPreferences(delegate);
List<BtcNode> nodes = preferences.selectPreferredNodes(mock(BtcNodes.class));
assertTrue(nodes.isEmpty());
}
@Test
public void testSelectPreferredNodesWhenCustomOption() {
Preferences delegate = mock(Preferences.class);
when(delegate.getBitcoinNodesOptionOrdinal()).thenReturn(CUSTOM.ordinal());
when(delegate.getBitcoinNodes()).thenReturn("aaa.onion,bbb.onion");
BtcNodesSetupPreferences preferences = new BtcNodesSetupPreferences(delegate);
List<BtcNode> nodes = preferences.selectPreferredNodes(mock(BtcNodes.class));
assertEquals(2, nodes.size());
}
}

View file

@ -0,0 +1,49 @@
/*
* 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.btc.wallet;
import org.bitcoinj.core.Coin;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@SuppressWarnings("ConstantConditions")
public class RestrictionsTest {
@Test
public void testIsMinSpendableAmount() {
Coin amount = null;
Coin txFee = Coin.valueOf(20000);
amount = Coin.ZERO;
assertFalse(Restrictions.isAboveDust(amount.subtract(txFee)));
amount = txFee;
assertFalse(Restrictions.isAboveDust(amount.subtract(txFee)));
amount = Restrictions.getMinNonDustOutput();
assertFalse(Restrictions.isAboveDust(amount.subtract(txFee)));
amount = txFee.add(Restrictions.getMinNonDustOutput());
assertTrue(Restrictions.isAboveDust(amount.subtract(txFee)));
amount = txFee.add(Restrictions.getMinNonDustOutput()).add(Coin.valueOf(1));
assertTrue(Restrictions.isAboveDust(amount.subtract(txFee)));
}
}

View file

@ -0,0 +1,61 @@
/*
* 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.crypto;
import bisq.common.crypto.CryptoException;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.KeyStorage;
import bisq.common.file.FileUtil;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.io.File;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.After;
import org.junit.Before;
public class EncryptionTest {
private static final Logger log = LoggerFactory.getLogger(EncryptionTest.class);
private KeyRing keyRing;
private File dir;
@Before
public void setup() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, CryptoException {
dir = File.createTempFile("temp_tests", "");
//noinspection ResultOfMethodCallIgnored
dir.delete();
//noinspection ResultOfMethodCallIgnored
dir.mkdir();
KeyStorage keyStorage = new KeyStorage(dir);
keyRing = new KeyRing(keyStorage);
}
@After
public void tearDown() throws IOException {
FileUtil.deleteDirectory(dir);
}
}

View file

@ -0,0 +1,89 @@
/*
* 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.crypto;
import bisq.common.crypto.CryptoException;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.KeyStorage;
import bisq.common.crypto.Sig;
import bisq.common.file.FileUtil;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
public class SigTest {
private static final Logger log = LoggerFactory.getLogger(SigTest.class);
private KeyRing keyRing;
private File dir;
@Before
public void setup() throws IOException {
dir = File.createTempFile("temp_tests", "");
//noinspection ResultOfMethodCallIgnored
dir.delete();
//noinspection ResultOfMethodCallIgnored
dir.mkdir();
KeyStorage keyStorage = new KeyStorage(dir);
keyRing = new KeyRing(keyStorage);
}
@After
public void tearDown() throws IOException {
FileUtil.deleteDirectory(dir);
}
@Test
public void testSignature() {
long ts = System.currentTimeMillis();
log.trace("start ");
for (int i = 0; i < 100; i++) {
String msg = String.valueOf(new Random().nextInt());
String sig = null;
try {
sig = Sig.sign(keyRing.getSignatureKeyPair().getPrivate(), msg);
} catch (CryptoException e) {
log.error("sign failed");
e.printStackTrace();
assertTrue(false);
}
try {
assertTrue(Sig.verify(keyRing.getSignatureKeyPair().getPublic(), msg, sig));
} catch (CryptoException e) {
log.error("verify failed");
e.printStackTrace();
assertTrue(false);
}
}
log.trace("took {} ms.", System.currentTimeMillis() - ts);
}
}

View file

@ -0,0 +1,46 @@
package bisq.core.dao.governance.ballot;
import bisq.core.dao.governance.ballot.BallotListService.BallotListChangeListener;
import bisq.core.dao.governance.period.PeriodService;
import bisq.core.dao.governance.proposal.ProposalService;
import bisq.core.dao.governance.proposal.ProposalValidatorProvider;
import bisq.core.dao.governance.proposal.storage.appendonly.ProposalPayload;
import bisq.common.persistence.PersistenceManager;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.junit.Test;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class BallotListServiceTest {
@Test
@SuppressWarnings("unchecked")
public void testAddListenersWhenNewPayloadAdded() {
// given
ObservableList<ProposalPayload> payloads = FXCollections.observableArrayList();
ProposalService proposalService = mock(ProposalService.class);
when(proposalService.getProposalPayloads()).thenReturn(payloads);
BallotListService service = new BallotListService(proposalService, mock(PeriodService.class),
mock(ProposalValidatorProvider.class), mock(PersistenceManager.class));
BallotListChangeListener listener = mock(BallotListChangeListener.class);
service.addListener(listener);
service.addListeners();
// when
payloads.add(mock(ProposalPayload.class, RETURNS_DEEP_STUBS));
// then
verify(listener).onListChanged(any());
}
}

View file

@ -0,0 +1,30 @@
package bisq.core.dao.governance.proposal;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.dao.governance.period.PeriodService;
import bisq.core.dao.state.DaoStateService;
import bisq.network.p2p.P2PService;
import bisq.common.crypto.PubKeyRing;
import bisq.common.persistence.PersistenceManager;
import javafx.beans.property.SimpleIntegerProperty;
import org.junit.Test;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class MyProposalListServiceTest {
@Test
public void canInstantiate() {
P2PService p2PService = mock(P2PService.class);
when(p2PService.getNumConnectedPeers()).thenReturn(new SimpleIntegerProperty(0));
PersistenceManager persistenceManager = mock(PersistenceManager.class);
MyProposalListService service = new MyProposalListService(p2PService,
mock(DaoStateService.class),
mock(PeriodService.class), mock(WalletsManager.class), persistenceManager, mock(PubKeyRing.class)
);
}
}

View file

@ -0,0 +1,127 @@
/*
* 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.dao.governance.proposal;
import bisq.core.dao.governance.period.PeriodService;
import bisq.core.dao.governance.proposal.storage.appendonly.ProposalStorageService;
import bisq.core.dao.governance.proposal.storage.temp.TempProposalPayload;
import bisq.core.dao.governance.proposal.storage.temp.TempProposalStorageService;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.governance.DaoPhase;
import bisq.core.dao.state.model.governance.Proposal;
import bisq.network.p2p.P2PService;
import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService;
import bisq.network.p2p.storage.persistence.ProtectedDataStoreService;
import javafx.collections.ListChangeListener;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import static org.mockito.Mockito.*;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* Tests of the P2PDataStorage::onRemoved callback behavior to ensure that the proper number of signal events occur.
*/
public class ProposalServiceP2PDataStorageListenerTest {
private ProposalService proposalService;
@Mock
private PeriodService periodService;
@Mock
private DaoStateService daoStateService;
@Mock
private ListChangeListener<Proposal> tempProposalListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
this.proposalService = new ProposalService(
mock(P2PService.class),
this.periodService,
mock(ProposalStorageService.class),
mock(TempProposalStorageService.class),
mock(AppendOnlyDataStoreService.class),
mock(ProtectedDataStoreService.class),
this.daoStateService,
mock(ProposalValidatorProvider.class),
true);
// Create a state so that all added/removed Proposals will actually update the tempProposals list.
when(this.periodService.isInPhase(anyInt(), any(DaoPhase.Phase.class))).thenReturn(true);
when(this.daoStateService.isParseBlockChainComplete()).thenReturn(false);
}
private static ProtectedStorageEntry buildProtectedStorageEntry() {
ProtectedStorageEntry protectedStorageEntry = mock(ProtectedStorageEntry.class);
TempProposalPayload tempProposalPayload = mock(TempProposalPayload.class);
Proposal tempProposal = mock(Proposal.class);
when(protectedStorageEntry.getProtectedStoragePayload()).thenReturn(tempProposalPayload);
when(tempProposalPayload.getProposal()).thenReturn(tempProposal);
return protectedStorageEntry;
}
// TESTCASE: If an onRemoved callback is called which does not remove anything the tempProposals listeners
// are not signaled.
@Test
public void onRemoved_noSignalIfNoChange() {
this.proposalService.onRemoved(Collections.singletonList(mock(ProtectedStorageEntry.class)));
verify(this.tempProposalListener, never()).onChanged(any());
}
// TESTCASE: If an onRemoved callback is called with 1 element AND it creates a remove of 1 element, the tempProposal
// listeners are signaled once.
@Test
public void onRemoved_signalOnceOnOneChange() {
ProtectedStorageEntry one = buildProtectedStorageEntry();
this.proposalService.onAdded(Collections.singletonList(one));
this.proposalService.getTempProposals().addListener(this.tempProposalListener);
this.proposalService.onRemoved(Collections.singletonList(one));
verify(this.tempProposalListener).onChanged(any());
}
// TESTCASE: If an onRemoved callback is called with 2 elements AND it creates a remove of 2 elements, the
// tempProposal listeners are signaled once.
@Test
public void onRemoved_signalOnceOnMultipleChanges() {
ProtectedStorageEntry one = buildProtectedStorageEntry();
ProtectedStorageEntry two = buildProtectedStorageEntry();
this.proposalService.onAdded(Arrays.asList(one, two));
this.proposalService.getTempProposals().addListener(this.tempProposalListener);
this.proposalService.onRemoved(Arrays.asList(one, two));
verify(this.tempProposalListener).onChanged(any());
}
}

View file

@ -0,0 +1,79 @@
/*
* 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.dao.governance.proposal.param;
import bisq.core.dao.governance.param.Param;
import bisq.core.locale.Res;
import bisq.core.util.coin.BsqFormatter;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class ChangeParamValidatorTest {
@Before
public void setup() {
Res.setup();
}
@Test
public void testGetChangeValidationResult() throws ParamValidationException {
ChangeParamValidator changeParamValidator = new ChangeParamValidator(null, null, new BsqFormatter());
try {
changeParamValidator.validationChange(0, 0, 2, 2, Param.UNDEFINED);
Assert.fail();
} catch (ParamValidationException e) {
Assert.assertEquals(e.getError(), ParamValidationException.ERROR.SAME);
}
try {
changeParamValidator.validationChange(0, 1, 2, 2, Param.UNDEFINED);
Assert.fail();
} catch (ParamValidationException e) {
Assert.assertEquals(e.getError(), ParamValidationException.ERROR.NO_CHANGE_POSSIBLE);
}
try {
changeParamValidator.validationChange(0, -1, 2, 2, Param.UNDEFINED);
Assert.fail();
} catch (ParamValidationException e) {
Assert.assertEquals(e.getError(), ParamValidationException.ERROR.NO_CHANGE_POSSIBLE);
}
try {
changeParamValidator.validationChange(2, 4, 2, 1.1, Param.UNDEFINED);
Assert.fail();
} catch (ParamValidationException e) {
Assert.assertEquals(e.getError(), ParamValidationException.ERROR.TOO_HIGH);
}
try {
changeParamValidator.validationChange(4, 2, 1.5, 2, Param.UNDEFINED);
Assert.fail();
} catch (ParamValidationException e) {
Assert.assertEquals(e.getError(), ParamValidationException.ERROR.TOO_LOW);
}
changeParamValidator.validationChange(4, 2, 2, 2, Param.UNDEFINED);
changeParamValidator.validationChange(2, 4, 2, 2, Param.UNDEFINED);
changeParamValidator.validationChange(0, 1, 0, 0, Param.UNDEFINED);
changeParamValidator.validationChange(0, -1, 0, 0, Param.UNDEFINED);
changeParamValidator.validationChange(-1, 0, 0, 0, Param.UNDEFINED);
changeParamValidator.validationChange(1, 0, 0, 0, Param.UNDEFINED);
}
}

View file

@ -0,0 +1,205 @@
/*
* 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.dao.node.full;
// not converting this test because it is already ignored.
// Intro to jmockit can be found at http://jmockit.github.io/tutorial/Mocking.html
//@Ignore
/*
public class BlockParserTest {
// @Tested classes are instantiated automatically when needed in a test case,
// using injection where possible, see http://jmockit.github.io/tutorial/Mocking.html#tested
// To force instantiate earlier, use availableDuringSetup
@Tested(fullyInitialized = true, availableDuringSetup = true)
BlockParser blockParser;
@Tested(fullyInitialized = true, availableDuringSetup = true)
DaoStateService daoStateService;
// @Injectable are mocked resources used to for injecting into @Tested classes
// The naming of these resources doesn't matter, any resource that fits will be used for injection
// Used by daoStateService
@Injectable
PersistenceProtoResolver persistenceProtoResolver;
@Injectable
File storageDir;
@Injectable
String genesisTxId = "genesisTxId";
@Injectable
int genesisBlockHeight = 200;
// Used by fullNodeParser
@Injectable
RpcService rpcService;
@Tested(fullyInitialized = true, availableDuringSetup = true)
DaoStateService writeModel;
@Tested(fullyInitialized = true, availableDuringSetup = true)
TxParser txParser;
//FIXME
@Test
public void testIsBsqTx() {
// Setup a basic transaction with two inputs
int height = 200;
String hash = "abc123";
long time = new Date().getTime();
final List<TxInput> inputs = asList(new TxInput("tx1", 0, null),
new TxInput("tx1", 1, null));
final List<RawTxOutput> outputs = asList(new RawTxOutput(0, 101, "tx1", null, null, null, height));
RawTx rawTx = new RawTx("vo", height, hash, time,
ImmutableList.copyOf(inputs),
ImmutableList.copyOf(outputs));
// Return one spendable txoutputs with value, for three test cases
// 1) - null, 0 -> not BSQ transaction
// 2) - 100, null -> BSQ transaction
// 3) - 0, 100 -> BSQ transaction
new Expectations(daoStateService) {{
// Expectations can be recorded on mocked instances, either with specific matching arguments or catch all
// http://jmockit.github.io/tutorial/Mocking.html#results
// Results are returned in the order they're recorded, so in this case for the first call to
// getSpendableTxOutput("tx1", 0) the return value will be Optional.empty()
// for the second call the return is Optional.of(new TxOutput(0,... and so on
daoStateService.getUnspentTxOutput(new TxOutputKey("tx1", 0));
result = Optional.empty();
result = Optional.of(new RawTxOutput(0, 100, "txout1", null, null, null, height));
result = Optional.of(new RawTxOutput(0, 0, "txout1", null, null, null, height));
daoStateService.getUnspentTxOutput(new TxOutputKey("tx1", 1));
result = Optional.of(new RawTxOutput(0, 0, "txout2", null, null, null, height));
result = Optional.empty();
result = Optional.of(new RawTxOutput(0, 100, "txout2", null, null, null, height));
}};
String genesisTxId = "genesisTxId";
int blockHeight = 200;
String blockHash = "abc123";
Coin genesisTotalSupply = Coin.parseCoin("2.5");
// First time there is no BSQ value to spend so it's not a bsq transaction
assertFalse(txParser.findTx(rawTx, genesisTxId, blockHeight, genesisTotalSupply).isPresent());
// Second time there is BSQ in the first txout
assertTrue(txParser.findTx(rawTx, genesisTxId, blockHeight, genesisTotalSupply).isPresent());
// Third time there is BSQ in the second txout
assertTrue(txParser.findTx(rawTx, genesisTxId, blockHeight, genesisTotalSupply).isPresent());
}
@Test
public void testParseBlocks() {
// Setup blocks to test, starting before genesis
// Only the transactions related to bsq are relevant, no checks are done on correctness of blocks or other txs
// so hashes and most other data don't matter
long time = new Date().getTime();
int genesisHeight = 200;
int startHeight = 199;
int headHeight = 201;
Coin issuance = Coin.parseCoin("2.5");
RawTransaction genTx = new RawTransaction("gen block hash", 0, 0L, 0L, genesisTxId);
// Blockhashes
String bh199 = "blockhash199";
String bh200 = "blockhash200";
String bh201 = "blockhash201";
// Block 199
String cbId199 = "cbid199";
RawTransaction tx199 = new RawTransaction(bh199, 0, 0L, 0L, cbId199);
RawTx cbTx199 = new RawTx(cbId199, 199, bh199, time,
ImmutableList.copyOf(new ArrayList<TxInput>()),
ImmutableList.copyOf(asList(new RawTxOutput(0, 25, cbId199, null, null, null, 199))));
RawBlock block199 = new RawBlock(bh199, 10, 10, 199, 2, "root", asList(tx199), time, Long.parseLong("1234"), "bits", BigDecimal.valueOf(1), "chainwork", "previousBlockHash", bh200);
// Genesis Block
String cbId200 = "cbid200";
RawTransaction tx200 = new RawTransaction(bh200, 0, 0L, 0L, cbId200);
RawTx cbTx200 = new RawTx(cbId200, 200, bh200, time,
ImmutableList.copyOf(new ArrayList<TxInput>()),
ImmutableList.copyOf(asList(new RawTxOutput(0, 25, cbId200, null, null, null, 200))));
RawTx genesisTx = new RawTx(genesisTxId, 200, bh200, time,
ImmutableList.copyOf(asList(new TxInput("someoldtx", 0, null))),
ImmutableList.copyOf(asList(new RawTxOutput(0, issuance.getValue(), genesisTxId, null, null, null, 200))));
RawBlock block200 = new RawBlock(bh200, 10, 10, 200, 2, "root", asList(tx200, genTx), time, Long.parseLong("1234"), "bits", BigDecimal.valueOf(1), "chainwork", bh199, bh201);
// Block 201
// Make a bsq transaction
String cbId201 = "cbid201";
String bsqTx1Id = "bsqtx1";
RawTransaction tx201 = new RawTransaction(bh201, 0, 0L, 0L, cbId201);
RawTransaction txbsqtx1 = new RawTransaction(bh201, 0, 0L, 0L, bsqTx1Id);
long bsqTx1Value1 = Coin.parseCoin("2.4").getValue();
long bsqTx1Value2 = Coin.parseCoin("0.04").getValue();
RawTx cbTx201 = new RawTx(cbId201, 201, bh201, time,
ImmutableList.copyOf(new ArrayList<TxInput>()),
ImmutableList.copyOf(asList(new RawTxOutput(0, 25, cbId201, null, null, null, 201))));
RawTx bsqTx1 = new RawTx(bsqTx1Id, 201, bh201, time,
ImmutableList.copyOf(asList(new TxInput(genesisTxId, 0, null))),
ImmutableList.copyOf(asList(new RawTxOutput(0, bsqTx1Value1, bsqTx1Id, null, null, null, 201),
new RawTxOutput(1, bsqTx1Value2, bsqTx1Id, null, null, null, 201))));
RawBlock block201 = new RawBlock(bh201, 10, 10, 201, 2, "root", asList(tx201, txbsqtx1), time, Long.parseLong("1234"), "bits", BigDecimal.valueOf(1), "chainwork", bh200, "nextBlockHash");
// TODO update test with new API
/*
new Expectations(rpcService) {{
rpcService.requestBlock(199);
result = block199;
rpcService.requestBlock(200);
result = block200;
rpcService.requestBlock(201);
result = block201;
rpcService.requestTx(cbId199, 199);
result = cbTx199;
rpcService.requestTx(cbId200, genesisHeight);
result = cbTx200;
rpcService.requestTx(genesisTxId, genesisHeight);
result = genesisTx;
rpcService.requestTx(cbId201, 201);
result = cbTx201;
rpcService.requestTx(bsqTx1Id, 201);
result = bsqTx1;
}};
// Running parseBlocks to build the bsq blockchain
fullNodeParser.parseBlocks(startHeight, headHeight, block -> {
});
*/
// Verify that the genesis tx has been added to the bsq blockchain with the correct issuance amount
/* assertTrue(daoStateService.getGenesisTx().get() == genesisTx);
assertTrue(daoStateService.getGenesisTotalSupply().getValue() == issuance.getValue());
// And that other txs are not added
assertFalse(daoStateService.containsTx(cbId199));
assertFalse(daoStateService.containsTx(cbId200));
assertFalse(daoStateService.containsTx(cbId201));
// But bsq txs are added
assertTrue(daoStateService.containsTx(bsqTx1Id));
TxOutput bsqOut1 = daoStateService.getUnspentAndMatureTxOutput(bsqTx1Id, 0).get();
assertTrue(daoStateService.isUnspent(bsqOut1));
assertTrue(bsqOut1.getValue() == bsqTx1Value1);
TxOutput bsqOut2 = daoStateService.getUnspentAndMatureTxOutput(bsqTx1Id, 1).get();
assertTrue(daoStateService.isUnspent(bsqOut2));
assertTrue(bsqOut2.getValue() == bsqTx1Value2);
assertFalse(daoStateService.isTxOutputSpendable(genesisTxId, 0));
assertTrue(daoStateService.isTxOutputSpendable(bsqTx1Id, 0));
assertTrue(daoStateService.isTxOutputSpendable(bsqTx1Id, 1));
}
}
*/

View file

@ -0,0 +1,140 @@
/*
* 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.dao.node.full;
import bisq.core.dao.node.full.rpc.dto.RawDtoInput;
import bisq.core.dao.node.full.rpc.dto.DtoSignatureScript;
import java.util.List;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class RpcServiceTest {
private static final String SIGNATURE = "3045" +
"022100b6c2fa10587d6fed3a0eecfd098b160f69a850beca139fe03ef65bec4cba1c5b" +
"02204a833a16c22bbd32722243ea3270e672f646ee9406e8797e11093951e92efbd5";
private static final String SIGNATURE_1 = "3044" +
"02201f00d9a4aab1a3a239f1ad95a910092c0c55423480d609eaad4599cf7ecb7f48" +
"0220668b1a9cf5624b1c4ece6da3f64bc6021e509f588ae1006601acd8a9f83b3576";
private static final String SIGNATURE_2 = "3045" +
"022100982eca77a72a2bdba51b9231afd4521400bee1bb7830634eb26db2b0c621bc46" +
"022073d7325916e2b5ceb1d2e510a5161fd9115105a8dafa94068864624bb10d190e";
private static final String PUB_KEY =
"03dcca91c2ec7229f1b4f4c4f664c92d3303dddef8d38736f6a7f28de16f3ce416";
private static final String PUB_KEY_1 =
"0229713ad5c604c585128b3a5da6de20d78fc33bd3b595e9991f4c0e1fee99f845";
private static final String PUB_KEY_2 =
"0398ad45a74bf5a5c5a8ec31de6815d2e805a23e68c0f8001770e74bc4c17c5b31";
private static final String MULTISIG_REDEEM_SCRIPT_HEX =
"5221" + PUB_KEY_1 + "21" + PUB_KEY_2 + "52ae"; // OP_2 pub1 pub2 OP_2 OP_CHECKMULTISIG
private static final String P2WPKH_REDEEM_SCRIPT_HEX =
"0014" + "9bc809698674ec7c01d35d438e9d0de1aa87b6c8"; // 0 hash160
private static final String P2WSH_REDEEM_SCRIPT_HEX =
"0020" + "223d978073802f79e6ecdc7591e5dc1f0ea7030d6466f73c6b90391bc72e886f"; // 0 hash256
@Test
public void testExtractPubKeyAsHex_coinbase() {
checkExtractPubKeyAsHexReturnsNull(new RawDtoInput());
}
@Test
public void testExtractPubKeyAsHex_P2PK() {
var input = rawInput(SIGNATURE + "[ALL]");
checkExtractPubKeyAsHexReturnsNull(input);
}
@Test
public void testExtractPubKeyAsHex_P2PKH() {
var input = rawInput(SIGNATURE + "[ALL] " + PUB_KEY);
assertEquals(PUB_KEY, RpcService.extractPubKeyAsHex(input, true));
assertEquals(PUB_KEY, RpcService.extractPubKeyAsHex(input, false));
}
@Test
public void testExtractPubKeyAsHex_P2WPKH() {
var input = rawInput("", SIGNATURE + "01", PUB_KEY);
assertEquals(PUB_KEY, RpcService.extractPubKeyAsHex(input, true));
assertNull(RpcService.extractPubKeyAsHex(input, false));
}
@Test
public void testExtractPubKeyAsHex_P2SH_P2WPKH() {
var input = rawInput(P2WPKH_REDEEM_SCRIPT_HEX, SIGNATURE + "01", PUB_KEY);
assertEquals(PUB_KEY, RpcService.extractPubKeyAsHex(input, true));
assertNull(RpcService.extractPubKeyAsHex(input, false));
}
@Test
public void testExtractPubKeyAsHex_P2PKH_nonDefaultSighash() {
var input = rawInput(SIGNATURE + "[SINGLE|ANYONECANPAY] " + PUB_KEY);
checkExtractPubKeyAsHexReturnsNull(input);
}
@Test
public void testExtractPubKeyAsHex_P2WPKH_nonDefaultSighash() {
var input = rawInput("", SIGNATURE + "82", PUB_KEY);
checkExtractPubKeyAsHexReturnsNull(input);
}
@Test
public void testExtractPubKeyAsHex_P2SH_P2WPKH_nonDefaultSighash() {
var input = rawInput(P2WPKH_REDEEM_SCRIPT_HEX, SIGNATURE + "82", PUB_KEY);
checkExtractPubKeyAsHexReturnsNull(input);
}
@Test
public void testExtractPubKeyAsHex_P2SH_multisig() {
var input = rawInput("0 " + SIGNATURE_1 + "[ALL] " + SIGNATURE_2 + "[ALL] " + MULTISIG_REDEEM_SCRIPT_HEX);
checkExtractPubKeyAsHexReturnsNull(input);
}
@Test
public void testExtractPubKeyAsHex_P2SH_multisig_nonDefaultSighash() {
var input = rawInput("0 " + SIGNATURE_1 + "[ALL] " + SIGNATURE_2 + "[NONE] " + MULTISIG_REDEEM_SCRIPT_HEX);
checkExtractPubKeyAsHexReturnsNull(input);
}
@Test
public void testExtractPubKeyAsHex_P2WSH_multisig() {
var input = rawInput("", "", SIGNATURE_1 + "01", SIGNATURE_2 + "01", MULTISIG_REDEEM_SCRIPT_HEX);
checkExtractPubKeyAsHexReturnsNull(input);
}
@Test
public void testExtractPubKeyAsHex_P2SH_P2WSH_multisig() {
var input = rawInput(P2WSH_REDEEM_SCRIPT_HEX, "", SIGNATURE_1 + "01", SIGNATURE_2 + "01", MULTISIG_REDEEM_SCRIPT_HEX);
checkExtractPubKeyAsHexReturnsNull(input);
}
private static void checkExtractPubKeyAsHexReturnsNull(RawDtoInput input) {
assertNull(RpcService.extractPubKeyAsHex(input, true));
assertNull(RpcService.extractPubKeyAsHex(input, false));
}
private static RawDtoInput rawInput(String asm, String... txInWitness) {
var input = new RawDtoInput();
var scriptSig = new DtoSignatureScript();
scriptSig.setAsm(asm);
input.setScriptSig(scriptSig);
input.setTxInWitness(txInWitness.length > 0 ? List.of(txInWitness) : null);
return input;
}
}

View file

@ -0,0 +1,234 @@
/*
* 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.dao.node.full.rpc;
import bisq.core.dao.node.full.rpc.dto.RawDtoBlock;
import bisq.core.dao.node.full.rpc.dto.RawDtoTransaction;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.junit.Before;
import org.junit.Test;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.googlecode.jsonrpc4j.HttpException;
import com.googlecode.jsonrpc4j.JsonRpcClientException;
import com.googlecode.jsonrpc4j.RequestIDGenerator;
import kotlin.text.Charsets;
public class BitcoindClientTest {
private static final String TEST_BLOCK_HASH = "015f37a20d517645a11a6cdd316049f41bc77b4a4057b2dd092114b78147f42c";
private static final String TEST_BLOCK_VERBOSITY_0 = readFromResourcesUnPrettified("getblock-result-verbosity-0.txt");
private static final String TEST_BLOCK_VERBOSITY_1 = readFromResourcesUnPrettified("getblock-result-verbosity-1.json");
private static final String TEST_BLOCK_VERBOSITY_2 = readFromResourcesUnPrettified("getblock-result-verbosity-2.json");
private static final String TEST_NETWORK_INFO = readFromResourcesUnPrettified("getnetworkinfo-result.json");
private BitcoindClient client;
private int mockResponseCode = 200;
private boolean canConnect = true;
private ByteArrayInputStream mockResponse;
private ByteArrayInputStream mockErrorResponse;
private ByteArrayOutputStream mockOutputStream = new ByteArrayOutputStream();
@Before
public void setUp() throws Exception {
var mockURLStreamHandler = mock(MyURLStreamHandler.class);
var mockRequestIDGenerator = mock(RequestIDGenerator.class);
client = BitcoindClient.builder()
.rpcHost("127.0.0.1")
.rpcPort(18443)
.rpcUser("bisqdao")
.rpcPassword("bsq")
.urlStreamHandler(mockURLStreamHandler)
.requestIDGenerator(mockRequestIDGenerator)
.build();
when(mockURLStreamHandler.openConnection(any(), any())).then(inv -> {
var connection = mock(HttpURLConnection.class);
if (canConnect) {
when(connection.getOutputStream()).thenReturn(mockOutputStream);
if (mockResponseCode < 400) {
when(connection.getInputStream()).thenReturn(mockResponse);
} else {
when(connection.getInputStream()).thenThrow(IOException.class);
when(connection.getErrorStream()).thenReturn(mockErrorResponse);
}
} else {
doThrow(ConnectException.class).when(connection).connect();
}
return connection;
});
when(mockRequestIDGenerator.generateID()).thenReturn("987654321");
}
@Test
public void testGetBlockCount() throws Exception {
var expectedRequest = toJson("{'id':'987654321','jsonrpc':'2.0','method':'getblockcount','params':[]}");
mockResponse = toJsonIS("{'result':'150','error':null,'id':'123456789'}");
assertEquals((Integer) 150, client.getBlockCount());
assertEquals(expectedRequest, mockOutputStream.toString(UTF_8));
}
@Test(expected = ConnectException.class)
public void testGetBlockCount_noConnection() throws Exception {
canConnect = false;
client.getBlockCount();
}
@Test(expected = HttpException.class)
public void testGetBlockCount_wrongCredentials() throws Exception {
mockResponseCode = 401;
// mockResponseCustomHeaders.put("WWW-Authenticate", "[Basic realm=\"jsonrpc\"]");
client.getBlockCount();
}
@Test
public void testGetBlockHash() throws Exception {
var expectedRequest = toJson("{'id':'987654321','jsonrpc':'2.0','method':'getblockhash','params':[139]}");
mockResponse = toJsonIS("{'result':'" + TEST_BLOCK_HASH + "','error':null,'id':'123456789'}");
assertEquals(TEST_BLOCK_HASH, client.getBlockHash(139));
assertEquals(expectedRequest, mockOutputStream.toString(UTF_8));
}
@Test
public void testGetBestBlockHash() throws Exception {
var expectedRequest = toJson("{'id':'987654321','jsonrpc':'2.0','method':'getbestblockhash','params':[]}");
mockResponse = toJsonIS("{'result':'" + TEST_BLOCK_HASH + "','error':null,'id':'123456789'}");
assertEquals(TEST_BLOCK_HASH, client.getBestBlockHash());
assertEquals(expectedRequest, mockOutputStream.toString(UTF_8));
}
@Test(expected = JsonRpcClientException.class)
public void testGetBlockHash_heightOutOfRange() throws Exception {
mockResponseCode = 500;
mockErrorResponse = toJsonIS("{'result':null,'error':{'code':-8,'message':'Block height out of range'},'id':'123456789'}");
client.getBlockHash(151);
}
@Test
public void testGetBlock_verbosity_0() throws Exception {
doTestGetBlock(0, "\"" + TEST_BLOCK_VERBOSITY_0 + "\"");
}
@Test
public void testGetBlock_verbosity_1() throws Exception {
doTestGetBlock(1, TEST_BLOCK_VERBOSITY_1);
}
@Test
public void testGetBlock_verbosity_2() throws Exception {
doTestGetBlock(2, TEST_BLOCK_VERBOSITY_2);
}
private void doTestGetBlock(int verbosity, String blockJson) throws Exception {
var expectedRequest = toJson("{'id':'987654321','jsonrpc':'2.0','method':'getblock','params':['"
+ TEST_BLOCK_HASH + "'," + verbosity + "]}");
mockResponse = toJsonIS("{'result':" + blockJson + ",'error':null,'id':'123456789'}");
var block = client.getBlock(TEST_BLOCK_HASH, verbosity);
var blockJsonRoundTripped = new ObjectMapper().writeValueAsString(block);
assertEquals(verbosity == 0, block instanceof RawDtoBlock.Summarized);
assertEquals(verbosity == 1, block.getTx() != null &&
block.getTx().stream().allMatch(tx -> tx instanceof RawDtoTransaction.Summarized));
assertEquals(blockJson, blockJsonRoundTripped);
assertEquals(expectedRequest, mockOutputStream.toString(UTF_8));
}
@Test(expected = JsonRpcClientException.class)
public void testGetBlock_blockNotFound() throws Exception {
mockResponseCode = 500;
mockErrorResponse = toJsonIS("{'result':null,'error':{'code':-5,'message':'Block not found'},'id':'123456789'}");
client.getBlock(TEST_BLOCK_HASH.replace('f', 'e'), 2);
}
@Test(expected = JsonRpcClientException.class)
public void testGetBlock_malformedHash() throws Exception {
mockResponseCode = 500;
mockErrorResponse = toJsonIS("{'result':null,'error':{'code':-8,'message':'blockhash must be of length 64 " +
"(not 3, for \\'foo\\')'},'id':'123456789'}");
client.getBlock("foo", 2);
}
@Test
public void testGetNetworkInfo() throws Exception {
var expectedRequest = toJson("{'id':'987654321','jsonrpc':'2.0','method':'getnetworkinfo','params':[]}");
mockResponse = toJsonIS("{'result':" + TEST_NETWORK_INFO + ",'error':null,'id':'123456789'}");
var networkInfo = client.getNetworkInfo();
var networkInfoRoundTripped = new ObjectMapper().writeValueAsString(networkInfo);
var expectedNetworkInfoStr = TEST_NETWORK_INFO.replace("MY_CUSTOM_SERVICE", "UNKNOWN");
assertEquals(expectedNetworkInfoStr, networkInfoRoundTripped);
assertEquals(expectedRequest, mockOutputStream.toString(UTF_8));
}
private static String toJson(String json) {
return json.replace("'", "\"").replace("\\\"", "'");
}
private static ByteArrayInputStream toJsonIS(String json) {
return new ByteArrayInputStream(toJson(json).getBytes(UTF_8));
}
private static String readFromResourcesUnPrettified(String resourceName) {
try {
var path = Paths.get(BitcoindClientTest.class.getResource(resourceName).toURI());
return new String(Files.readAllBytes(path), Charsets.UTF_8).replaceAll("(\\s+\\B|\\B\\s+|\\v)", "");
} catch (Exception e) {
return "";
}
}
private static abstract class MyURLStreamHandler extends URLStreamHandler {
@Override
public abstract URLConnection openConnection(URL u, Proxy p);
}
}

View file

@ -0,0 +1,183 @@
/*
* 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.dao.node.full.rpc;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.mockito.Mockito.*;
public class BitcoindDaemonTest {
private BitcoindDaemon daemon;
private int acceptAnotherCount;
private CountDownLatch errorHandlerLatch = new CountDownLatch(1);
private Consumer<Throwable> errorHandler = mock(ThrowableConsumer.class);
private BitcoindDaemon.BlockListener blockListener = mock(BitcoindDaemon.BlockListener.class);
private Socket socket = mock(Socket.class);
private volatile boolean socketClosed;
@Before
public void setUp() throws Exception {
var serverSocket = mock(ServerSocket.class);
when(serverSocket.accept()).then(invocation -> waitToAccept(() -> {
if (socketClosed) {
throw new SocketException();
}
return socket;
}));
doAnswer((VoidAnswer) invocation -> {
socketClosed = true;
acceptAnother(1);
}).when(serverSocket).close();
doAnswer((VoidAnswer) invocation -> errorHandlerLatch.countDown()).when(errorHandler).accept(any());
daemon = new BitcoindDaemon(serverSocket, errorHandler);
daemon.setBlockListener(blockListener);
}
@After
public void tearDown() {
daemon.shutdown();
}
@Test
public void testNoBlocksMissedDuringFloodOfIncomingBlocks() throws Exception {
var latch = new CountDownLatch(1); // to block all the daemon worker threads until shutdown, as if stuck
doAnswer((VoidAnswer) invocation -> latch.await()).when(blockListener).blockDetected(any());
when(socket.getInputStream()).then(invocation -> new ByteArrayInputStream("foo".getBytes()));
acceptAnother(50);
waitUntilAllAccepted();
// Unblock all the daemon worker threads and shut down.
latch.countDown();
daemon.shutdown();
verify(blockListener, times(50)).blockDetected("foo");
}
@Test
public void testBlockHashIsTrimmed() throws Exception {
when(socket.getInputStream()).then(invocation -> new ByteArrayInputStream("\r\nbar \n".getBytes()));
acceptAnother(1);
waitUntilAllAccepted();
daemon.shutdown();
verify(blockListener).blockDetected("bar");
}
@Test
public void testBrokenSocketRead() throws Exception {
when(socket.getInputStream()).thenThrow(IOException.class);
acceptAnother(1);
errorHandlerLatch.await(5, TimeUnit.SECONDS);
verify(errorHandler).accept(argThat(t -> t instanceof NotificationHandlerException &&
t.getCause() instanceof IOException));
}
@Test
public void testRuntimeExceptionInBlockListener() throws Exception {
daemon.setBlockListener(blockHash -> {
throw new IndexOutOfBoundsException();
});
when(socket.getInputStream()).then(invocation -> new ByteArrayInputStream("foo".getBytes()));
acceptAnother(1);
errorHandlerLatch.await(5, TimeUnit.SECONDS);
verify(errorHandler).accept(argThat(t -> t instanceof NotificationHandlerException &&
t.getCause() instanceof IndexOutOfBoundsException));
}
@Test
public void testErrorInBlockListener() throws Exception {
synchronized (this) {
daemon.setBlockListener(blockHash -> {
throw new Error();
});
when(socket.getInputStream()).then(invocation -> new ByteArrayInputStream("foo".getBytes()));
acceptAnother(1);
}
errorHandlerLatch.await(5, TimeUnit.SECONDS);
verify(errorHandler).accept(any(Error.class));
}
@Test(expected = NotificationHandlerException.class)
public void testUnknownHost() throws Exception {
new BitcoindDaemon("[", -1, errorHandler).shutdown();
}
private synchronized void acceptAnother(int n) {
acceptAnotherCount += n;
notifyAll();
}
private synchronized <V> V waitToAccept(Callable<V> onAccept) throws Exception {
while (acceptAnotherCount == 0) {
wait();
}
var result = onAccept.call();
acceptAnotherCount--;
notifyAll();
return result;
}
private synchronized void waitUntilAllAccepted() throws InterruptedException {
while (acceptAnotherCount > 0) {
wait();
}
notifyAll();
}
private interface ThrowableConsumer extends Consumer<Throwable> {
}
private interface VoidAnswer extends Answer<Void> {
void voidAnswer(InvocationOnMock invocation) throws Throwable;
@Override
default Void answer(InvocationOnMock invocation) throws Throwable {
voidAnswer(invocation);
return null;
}
}
}

View file

@ -0,0 +1,200 @@
/*
* 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.dao.node.parser;
import bisq.core.dao.node.full.RawTx;
import bisq.core.dao.node.full.RawTxOutput;
import bisq.core.dao.node.parser.exceptions.InvalidGenesisTxException;
import bisq.core.dao.state.model.blockchain.TxInput;
import bisq.core.dao.state.model.blockchain.TxOutputType;
import bisq.core.dao.state.model.blockchain.TxType;
import org.bitcoinj.core.Coin;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
public class GenesisTxParserTest {
@Test
public void testIsGenesis() {
// fixme(chirhonul): Assert.assertEquals(2, 3);
int blockHeight = 200;
String blockHash = "abc123";
Coin genesisTotalSupply = Coin.parseCoin("2.5");
long time = new Date().getTime();
final List<TxInput> inputs = Arrays.asList(
new TxInput("tx0", 0, null),
new TxInput("tx1", 1, null)
);
RawTxOutput output = new RawTxOutput(
0,
genesisTotalSupply.value,
null,
null,
null,
null,
blockHeight
);
RawTx rawTx = new RawTx(
"tx2",
blockHeight,
blockHash,
time,
ImmutableList.copyOf(inputs),
ImmutableList.copyOf(Arrays.asList(output))
);
String genesisTxId = "genesisTxId";
int genesisBlockHeight = 150;
// With mismatch in block height and tx id, we should not get genesis tx back.
boolean result = GenesisTxParser.isGenesis(rawTx, genesisTxId, genesisBlockHeight);
boolean want = false;
Assert.assertEquals(want, result);
// With correct block height but mismatch in tx id, we should still not get genesis tx back.
blockHeight = 150;
rawTx = new RawTx(
"tx2",
blockHeight,
blockHash,
time,
ImmutableList.copyOf(inputs),
ImmutableList.copyOf(Arrays.asList(output))
);
result = GenesisTxParser.isGenesis(rawTx, genesisTxId, genesisBlockHeight);
want = false;
Assert.assertEquals(want, result);
}
@Test
public void testGetGenesisTempTx() {
int blockHeight = 200;
String blockHash = "abc123";
Coin genesisTotalSupply = Coin.parseCoin("2.5");
long time = new Date().getTime();
final List<TxInput> inputs = Arrays.asList(
new TxInput("tx0", 0, null),
new TxInput("tx1", 1, null)
);
RawTxOutput output = new RawTxOutput(
0,
genesisTotalSupply.value,
null,
null,
null,
null,
blockHeight
);
String genesisTxId = "genesisTxId";
// With correct tx id and block height, we should find our genesis tx with correct tx and output type.
RawTx rawTx = new RawTx(
genesisTxId,
blockHeight,
blockHash,
time,
ImmutableList.copyOf(inputs),
ImmutableList.copyOf(Arrays.asList(output))
);
TempTx resultTempTx = GenesisTxParser.getGenesisTempTx(rawTx, genesisTotalSupply);
TempTx tempTx = TempTx.fromRawTx(rawTx);
tempTx.setTxType(TxType.GENESIS);
for (int i = 0; i < tempTx.getTempTxOutputs().size(); ++i) {
tempTx.getTempTxOutputs().get(i).setTxOutputType(TxOutputType.GENESIS_OUTPUT);
}
TempTx wantTempTx = tempTx;
Assert.assertEquals(wantTempTx, resultTempTx);
// With correct tx id and block height, but too low sum of outputs (lower than genesisTotalSupply), we
// should see an exception raised.
output = new RawTxOutput(
0,
genesisTotalSupply.value - 1,
null,
null,
null,
null,
blockHeight
);
rawTx = new RawTx(
genesisTxId,
blockHeight,
blockHash,
time,
ImmutableList.copyOf(inputs),
ImmutableList.copyOf(Arrays.asList(output))
);
try {
GenesisTxParser.getGenesisTempTx(rawTx, genesisTotalSupply);
Assert.fail("Expected an InvalidGenesisTxException to be thrown when outputs are too low");
} catch (InvalidGenesisTxException igtxe) {
String wantMessage = "Genesis tx is invalid; not using all available inputs. Remaining input value is 1 sat";
Assert.assertTrue("Unexpected exception, want message starting with " +
"'" + wantMessage + "', got '" + igtxe.getMessage() + "'", igtxe.getMessage().startsWith(wantMessage));
}
// With correct tx id and block height, but too high sum of outputs (higher than from genesisTotalSupply), we
// should see an exception raised.
RawTxOutput output1 = new RawTxOutput(
0,
genesisTotalSupply.value - 2,
null,
null,
null,
null,
blockHeight
);
RawTxOutput output2 = new RawTxOutput(
0,
3,
null,
null,
null,
null,
blockHeight
);
rawTx = new RawTx(
genesisTxId,
blockHeight,
blockHash,
time,
ImmutableList.copyOf(inputs),
ImmutableList.copyOf(Arrays.asList(output1, output2))
);
try {
GenesisTxParser.getGenesisTempTx(rawTx, genesisTotalSupply);
Assert.fail("Expected an InvalidGenesisTxException to be thrown when outputs are too high");
} catch (InvalidGenesisTxException igtxe) {
String wantMessage = "Genesis tx is invalid; using more than available inputs. Remaining input value is 2 sat";
Assert.assertTrue("Unexpected exception, want message starting with " +
"'" + wantMessage + "', got '" + igtxe.getMessage() + "'", igtxe.getMessage().startsWith(wantMessage));
}
}
}

View file

@ -0,0 +1,72 @@
/*
* 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.dao.state;
import bisq.core.dao.state.model.DaoState;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.util.coin.BsqFormatter;
import org.bitcoinj.core.Coin;
import org.junit.Assert;
import org.junit.Test;
public class DaoStateServiceTest {
@Test
public void testIsBlockHashKnown() {
DaoStateService stateService = new DaoStateService(
new DaoState(),
new GenesisTxInfo("fakegenesistxid", 100, Coin.parseCoin("2.5").value),
new BsqFormatter());
Assert.assertEquals(
"Unknown block should not exist.",
false,
stateService.isBlockHashKnown("fakeblockhash0")
);
Block block = new Block(0, 1534800000, "fakeblockhash0", null);
stateService.onNewBlockHeight(0);
stateService.onNewBlockWithEmptyTxs(block);
Assert.assertEquals(
"Block has to be genesis block to get added.",
false,
stateService.isBlockHashKnown("fakeblockhash0")
);
Assert.assertEquals(
"Block that was never added should still not exist.",
false,
stateService.isBlockHashKnown("fakeblockhash1")
);
block = new Block(1, 1534800001, "fakeblockhash1", null);
stateService.onNewBlockHeight(1);
stateService.onNewBlockWithEmptyTxs(block);
block = new Block(2, 1534800002, "fakeblockhash2", null);
stateService.onNewBlockHeight(2);
stateService.onNewBlockWithEmptyTxs(block);
block = new Block(3, 1534800003, "fakeblockhash3", null);
stateService.onNewBlockHeight(3);
stateService.onNewBlockWithEmptyTxs(block);
Assert.assertEquals(
"Block that was never added should still not exist after adding more blocks.",
false,
stateService.isBlockHashKnown("fakeblockhash4")
);
}
}

View file

@ -0,0 +1,73 @@
/*
* 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.dao.state;
import bisq.core.dao.monitoring.DaoStateMonitoringService;
import bisq.core.dao.state.storage.DaoStateStorageService;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
public class DaoStateSnapshotServiceTest {
private DaoStateSnapshotService daoStateSnapshotService;
@Before
public void setup() {
daoStateSnapshotService = new DaoStateSnapshotService(mock(DaoStateService.class),
mock(GenesisTxInfo.class),
mock(DaoStateStorageService.class),
mock(DaoStateMonitoringService.class),
null);
}
@Test
public void testGetSnapshotHeight() {
assertEquals(120, daoStateSnapshotService.getSnapshotHeight(102, 0, 10));
assertEquals(120, daoStateSnapshotService.getSnapshotHeight(102, 100, 10));
assertEquals(120, daoStateSnapshotService.getSnapshotHeight(102, 102, 10));
assertEquals(120, daoStateSnapshotService.getSnapshotHeight(102, 119, 10));
assertEquals(120, daoStateSnapshotService.getSnapshotHeight(102, 120, 10));
assertEquals(120, daoStateSnapshotService.getSnapshotHeight(102, 121, 10));
assertEquals(120, daoStateSnapshotService.getSnapshotHeight(102, 130, 10));
assertEquals(120, daoStateSnapshotService.getSnapshotHeight(102, 139, 10));
assertEquals(130, daoStateSnapshotService.getSnapshotHeight(102, 140, 10));
assertEquals(130, daoStateSnapshotService.getSnapshotHeight(102, 141, 10));
assertEquals(990, daoStateSnapshotService.getSnapshotHeight(102, 1000, 10));
}
@Test
public void testSnapshotHeight() {
assertFalse(daoStateSnapshotService.isSnapshotHeight(102, 0, 10));
assertFalse(daoStateSnapshotService.isSnapshotHeight(102, 80, 10));
assertFalse(daoStateSnapshotService.isSnapshotHeight(102, 90, 10));
assertFalse(daoStateSnapshotService.isSnapshotHeight(102, 100, 10));
assertFalse(daoStateSnapshotService.isSnapshotHeight(102, 119, 10));
assertTrue(daoStateSnapshotService.isSnapshotHeight(102, 120, 10));
assertTrue(daoStateSnapshotService.isSnapshotHeight(102, 130, 10));
assertTrue(daoStateSnapshotService.isSnapshotHeight(102, 140, 10));
assertTrue(daoStateSnapshotService.isSnapshotHeight(102, 200, 10));
assertFalse(daoStateSnapshotService.isSnapshotHeight(102, 201, 10));
assertFalse(daoStateSnapshotService.isSnapshotHeight(102, 199, 10));
}
}

View file

@ -0,0 +1,107 @@
/*
* 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.dao.voting.voteresult;
import bisq.core.dao.governance.merit.MeritConsensus;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.junit.Assert.assertEquals;
@Slf4j
public class VoteResultConsensusTest {
@Before
public void setup() {
}
@Rule
public final ExpectedException exception = ExpectedException.none();
@Test
public void testGetWeightedMeritAmount() {
int currentChainHeight;
int blocksPerYear = 50_000; // 144*365=51264;
currentChainHeight = 1_000_000;
assertEquals("fresh issuance", 100000, MeritConsensus.getWeightedMeritAmount(100_000, 1_000_000,
currentChainHeight, blocksPerYear));
assertEquals("0.5 year old issuance", 75000, MeritConsensus.getWeightedMeritAmount(100_000, 975_000,
currentChainHeight, blocksPerYear));
assertEquals("1 year old issuance", 50000, MeritConsensus.getWeightedMeritAmount(100_000, 950_000,
currentChainHeight, blocksPerYear));
assertEquals("1.5 year old issuance", 25000, MeritConsensus.getWeightedMeritAmount(100_000, 925_000,
currentChainHeight, blocksPerYear));
assertEquals("2 year old issuance", 0, MeritConsensus.getWeightedMeritAmount(100_000, 900_000,
currentChainHeight, blocksPerYear));
assertEquals("3 year old issuance", 0, MeritConsensus.getWeightedMeritAmount(100_000, 850_000,
currentChainHeight, blocksPerYear));
assertEquals("1 block old issuance", 99999, MeritConsensus.getWeightedMeritAmount(100_000, 999_999,
currentChainHeight, blocksPerYear));
assertEquals("2 block old issuance", 99998, MeritConsensus.getWeightedMeritAmount(100_000, 999_998,
currentChainHeight, blocksPerYear));
assertEquals("10 blocks old issuance", 99990, MeritConsensus.getWeightedMeritAmount(100_000, 999_990,
currentChainHeight, blocksPerYear));
assertEquals("100 blocks old issuance", 99900, MeritConsensus.getWeightedMeritAmount(100_000, 999_900,
currentChainHeight, blocksPerYear));
assertEquals("99_999 blocks old issuance", 1, MeritConsensus.getWeightedMeritAmount(100_000, 900_001,
currentChainHeight, blocksPerYear));
assertEquals("99_990 blocks old issuance", 10, MeritConsensus.getWeightedMeritAmount(100_000, 900_010,
currentChainHeight, blocksPerYear));
assertEquals("100_001 blocks old issuance", 0, MeritConsensus.getWeightedMeritAmount(100_000, 899_999,
currentChainHeight, blocksPerYear));
assertEquals("1_000_000 blocks old issuance", 0, MeritConsensus.getWeightedMeritAmount(100_000, 0,
currentChainHeight, blocksPerYear));
}
@Test
public void testInvalidChainHeight() {
exception.expect(IllegalArgumentException.class);
MeritConsensus.getWeightedMeritAmount(100_000, 2_000_000, 1_000_000, 1_000_000);
}
@Test
public void testInvalidIssuanceHeight() {
exception.expect(IllegalArgumentException.class);
MeritConsensus.getWeightedMeritAmount(100_000, -1, 1_000_000, 1_000_000);
}
@Test
public void testInvalidAmount() {
exception.expect(IllegalArgumentException.class);
MeritConsensus.getWeightedMeritAmount(-100_000, 1, 1_000_000, 1_000_000);
}
@Test
public void testInvalidCurrentChainHeight() {
exception.expect(IllegalArgumentException.class);
MeritConsensus.getWeightedMeritAmount(100_000, 1, -1, 1_000_000);
}
@Test
public void testInvalidBlockPerYear() {
exception.expect(IllegalArgumentException.class);
MeritConsensus.getWeightedMeritAmount(100_000, 1, 11, -1);
}
}

View file

@ -0,0 +1,47 @@
package bisq.core.locale;
import java.util.Locale;
import org.junit.Before;
import org.junit.Test;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class BankUtilTest {
@Before
public void setup() {
Locale.setDefault(new Locale("en", "US"));
GlobalSettings.setLocale(new Locale("en", "US"));
Res.setBaseCurrencyCode("BTC");
Res.setBaseCurrencyName("Bitcoin");
}
@Test
public void testBankFieldsForArgentina() {
final String argentina = "AR";
assertTrue(BankUtil.isHolderIdRequired(argentina));
assertEquals("CUIL/CUIT", BankUtil.getHolderIdLabel(argentina));
assertEquals("CUIT", BankUtil.getHolderIdLabelShort(argentina));
assertTrue(BankUtil.isNationalAccountIdRequired(argentina));
assertEquals("CBU number", BankUtil.getNationalAccountIdLabel(argentina));
assertTrue(BankUtil.isBankNameRequired(argentina));
assertTrue(BankUtil.isBranchIdRequired(argentina));
assertTrue(BankUtil.isAccountNrRequired(argentina));
assertEquals("Número de cuenta", BankUtil.getAccountNrLabel(argentina));
assertTrue(BankUtil.useValidation(argentina));
assertFalse(BankUtil.isBankIdRequired(argentina));
assertFalse(BankUtil.isStateRequired(argentina));
assertFalse(BankUtil.isAccountTypeRequired(argentina));
}
}

View file

@ -0,0 +1,155 @@
/*
* 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.locale;
import bisq.common.config.BaseCurrencyNetwork;
import bisq.asset.Asset;
import bisq.asset.AssetRegistry;
import bisq.asset.Coin;
import bisq.asset.coins.Ether;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.stream.Stream;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class CurrencyUtilTest {
@Before
public void setup() {
Locale.setDefault(new Locale("en", "US"));
Res.setBaseCurrencyCode("BTC");
Res.setBaseCurrencyName("Bitcoin");
}
@Test
public void testGetTradeCurrency() {
Optional<TradeCurrency> euro = CurrencyUtil.getTradeCurrency("EUR");
Optional<TradeCurrency> naira = CurrencyUtil.getTradeCurrency("NGN");
Optional<TradeCurrency> fake = CurrencyUtil.getTradeCurrency("FAK");
assertTrue(euro.isPresent());
assertTrue(naira.isPresent());
assertFalse("Fake currency shouldn't exist", fake.isPresent());
}
@Test
public void testFindAsset() {
MockAssetRegistry assetRegistry = new MockAssetRegistry();
// test if code is matching
boolean daoTradingActivated = false;
// Test if BSQ on mainnet is failing
Assert.assertFalse(CurrencyUtil.findAsset(assetRegistry, "BSQ",
BaseCurrencyNetwork.BTC_MAINNET, daoTradingActivated).isPresent());
// on testnet/regtest it is allowed
assertEquals(CurrencyUtil.findAsset(assetRegistry, "BSQ",
BaseCurrencyNetwork.BTC_TESTNET, daoTradingActivated).get().getTickerSymbol(), "BSQ");
daoTradingActivated = true;
// With daoTradingActivated we can request BSQ
assertEquals(CurrencyUtil.findAsset(assetRegistry, "BSQ",
BaseCurrencyNetwork.BTC_MAINNET, daoTradingActivated).get().getTickerSymbol(), "BSQ");
// Test if not matching ticker is failing
Assert.assertFalse(CurrencyUtil.findAsset(assetRegistry, "BSQ1",
BaseCurrencyNetwork.BTC_MAINNET, daoTradingActivated).isPresent());
// Add a mock coin which has no mainnet version, needs to fail if we are on mainnet
MockTestnetCoin.Testnet mockTestnetCoin = new MockTestnetCoin.Testnet();
try {
assetRegistry.addAsset(mockTestnetCoin);
CurrencyUtil.findAsset(assetRegistry, "MOCK_COIN",
BaseCurrencyNetwork.BTC_MAINNET, daoTradingActivated);
Assert.fail("Expected an IllegalArgumentException");
} catch (IllegalArgumentException e) {
String wantMessage = "We are on mainnet and we could not find an asset with network type mainnet";
Assert.assertTrue("Unexpected exception, want message starting with " +
"'" + wantMessage + "', got '" + e.getMessage() + "'", e.getMessage().startsWith(wantMessage));
}
// For testnet its ok
assertEquals(CurrencyUtil.findAsset(assetRegistry, "MOCK_COIN",
BaseCurrencyNetwork.BTC_TESTNET, daoTradingActivated).get().getTickerSymbol(), "MOCK_COIN");
assertEquals(Coin.Network.TESTNET, mockTestnetCoin.getNetwork());
// For regtest its still found
assertEquals(CurrencyUtil.findAsset(assetRegistry, "MOCK_COIN",
BaseCurrencyNetwork.BTC_REGTEST, daoTradingActivated).get().getTickerSymbol(), "MOCK_COIN");
// We test if we are not on mainnet to get the mainnet coin
Coin ether = new Ether();
assertEquals(CurrencyUtil.findAsset(assetRegistry, "ETH",
BaseCurrencyNetwork.BTC_TESTNET, daoTradingActivated).get().getTickerSymbol(), "ETH");
assertEquals(CurrencyUtil.findAsset(assetRegistry, "ETH",
BaseCurrencyNetwork.BTC_REGTEST, daoTradingActivated).get().getTickerSymbol(), "ETH");
assertEquals(Coin.Network.MAINNET, ether.getNetwork());
// We test if network matches exactly if there are distinct network types defined like with BSQ
Coin bsq = (Coin) CurrencyUtil.findAsset(assetRegistry, "BSQ", BaseCurrencyNetwork.BTC_MAINNET, daoTradingActivated).get();
assertEquals("BSQ", bsq.getTickerSymbol());
assertEquals(Coin.Network.MAINNET, bsq.getNetwork());
bsq = (Coin) CurrencyUtil.findAsset(assetRegistry, "BSQ", BaseCurrencyNetwork.BTC_TESTNET, daoTradingActivated).get();
assertEquals("BSQ", bsq.getTickerSymbol());
assertEquals(Coin.Network.TESTNET, bsq.getNetwork());
bsq = (Coin) CurrencyUtil.findAsset(assetRegistry, "BSQ", BaseCurrencyNetwork.BTC_REGTEST, daoTradingActivated).get();
assertEquals("BSQ", bsq.getTickerSymbol());
assertEquals(Coin.Network.REGTEST, bsq.getNetwork());
}
@Test
public void testGetNameAndCodeOfRemovedAsset() {
assertEquals("Bitcoin Cash (BCH)", CurrencyUtil.getNameAndCode("BCH"));
assertEquals("N/A (XYZ)", CurrencyUtil.getNameAndCode("XYZ"));
}
class MockAssetRegistry extends AssetRegistry {
private List<Asset> registeredAssets = new ArrayList<>();
MockAssetRegistry() {
for (Asset asset : ServiceLoader.load(Asset.class)) {
registeredAssets.add(asset);
}
}
void addAsset(Asset asset) {
registeredAssets.add(asset);
}
public Stream<Asset> stream() {
return registeredAssets.stream();
}
}
}

View file

@ -0,0 +1,69 @@
/*
* 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.locale;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.RegTestParams;
import org.bitcoinj.params.TestNet3Params;
import bisq.asset.AddressValidationResult;
import bisq.asset.Base58AddressValidator;
import bisq.asset.Coin;
public class MockTestnetCoin extends Coin {
public MockTestnetCoin(Network network, NetworkParameters networkParameters) {
super("MockTestnetCoin", "MOCK_COIN", new BSQAddressValidator(networkParameters), network);
}
public static class Mainnet extends MockTestnetCoin {
public Mainnet() {
super(Network.MAINNET, MainNetParams.get());
}
}
public static class Testnet extends MockTestnetCoin {
public Testnet() {
super(Network.TESTNET, TestNet3Params.get());
}
}
public static class Regtest extends MockTestnetCoin {
public Regtest() {
super(Network.REGTEST, RegTestParams.get());
}
}
public static class BSQAddressValidator extends Base58AddressValidator {
public BSQAddressValidator(NetworkParameters networkParameters) {
super(networkParameters);
}
@Override
public AddressValidationResult validate(String address) {
return super.validate(address);
}
}
}

View file

@ -0,0 +1,69 @@
/*
* 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.message;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
@Slf4j
public class MarshallerTest {
@Test
public void getBaseEnvelopeTest() {
protobuf.Ping Ping = protobuf.Ping.newBuilder().setNonce(100).build();
protobuf.Pong Pong = protobuf.Pong.newBuilder().setRequestNonce(1000).build();
protobuf.NetworkEnvelope envelope1 = protobuf.NetworkEnvelope.newBuilder().setPing(Ping).build();
protobuf.NetworkEnvelope envelope2 = protobuf.NetworkEnvelope.newBuilder().setPong(Pong).build();
log.info(Ping.toString());
log.info(Pong.toString());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
envelope1.writeDelimitedTo(outputStream);
envelope2.writeDelimitedTo(outputStream);
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
protobuf.NetworkEnvelope envelope3 = protobuf.NetworkEnvelope.parseDelimitedFrom(inputStream);
protobuf.NetworkEnvelope envelope4 = protobuf.NetworkEnvelope.parseDelimitedFrom(inputStream);
log.info("message: {}", envelope3.getPing());
//log.info("peerseesd empty: '{}'",envelope3.getPong().equals(PB.NetworkEnvelope.) == "");
assertTrue(isPing(envelope3));
assertTrue(!isPing(envelope4));
log.info("3 = {} 4 = {}", isPing(envelope3), isPing(envelope4));
log.info(envelope3.toString());
log.info(envelope4.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean isPing(protobuf.NetworkEnvelope envelope) {
return !envelope.getPing().getDefaultInstanceForType().equals(envelope.getPing());
}
}

View file

@ -0,0 +1,133 @@
/*
* 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.monetary;
import org.junit.Assert;
import org.junit.Test;
public class PriceTest {
@Test
public void testParse() {
Price result = Price.parse("USD", "0.1");
Assert.assertEquals(
"Fiat value should be formatted with two decimals.",
"0.10 BTC/USD",
result.toFriendlyString()
);
result = Price.parse("EUR", "0.1234");
Assert.assertEquals(
"Fiat value should be given two decimals",
"0.1234 BTC/EUR",
result.toFriendlyString()
);
try {
Price.parse("EUR", "0.12345");
Assert.fail("Expected IllegalArgumentException to be thrown when too many decimals are used.");
} catch (IllegalArgumentException iae) {
Assert.assertEquals(
"Unexpected exception message.",
"java.lang.ArithmeticException: Rounding necessary",
iae.getMessage()
);
}
Assert.assertEquals(
"Negative value should be parsed correctly.",
-100000000L,
Price.parse("LTC", "-1").getValue()
);
Assert.assertEquals(
"Comma (',') as decimal separator should be converted to period ('.')",
"0.0001 BTC/USD",
Price.parse("USD", "0,0001").toFriendlyString()
);
Assert.assertEquals(
"Too many decimals should get rounded up properly.",
"10000.2346 LTC/BTC",
Price.parse("LTC", "10000,23456789").toFriendlyString()
);
Assert.assertEquals(
"Too many decimals should get rounded down properly.",
"10000.2345 LTC/BTC",
Price.parse("LTC", "10000,23454999").toFriendlyString()
);
Assert.assertEquals(
"Underlying long value should be correct.",
1000023456789L,
Price.parse("LTC", "10000,23456789").getValue()
);
try {
Price.parse("XMR", "56789.123456789");
Assert.fail("Expected IllegalArgumentException to be thrown when too many decimals are used.");
} catch (IllegalArgumentException iae) {
Assert.assertEquals(
"Unexpected exception message.",
"java.lang.ArithmeticException: Rounding necessary",
iae.getMessage()
);
}
}
@Test
public void testValueOf() {
Price result = Price.valueOf("USD", 1);
Assert.assertEquals(
"Fiat value should have four decimals.",
"0.0001 BTC/USD",
result.toFriendlyString()
);
result = Price.valueOf("EUR", 1234);
Assert.assertEquals(
"Fiat value should be given two decimals",
"0.1234 BTC/EUR",
result.toFriendlyString()
);
Assert.assertEquals(
"Negative value should be parsed correctly.",
-1L,
Price.valueOf("LTC", -1L).getValue()
);
Assert.assertEquals(
"Too many decimals should get rounded up properly.",
"10000.2346 LTC/BTC",
Price.valueOf("LTC", 1000023456789L).toFriendlyString()
);
Assert.assertEquals(
"Too many decimals should get rounded down properly.",
"10000.2345 LTC/BTC",
Price.valueOf("LTC", 1000023454999L).toFriendlyString()
);
Assert.assertEquals(
"Underlying long value should be correct.",
1000023456789L,
Price.valueOf("LTC", 1000023456789L).getValue()
);
}
}

View file

@ -0,0 +1,48 @@
/*
* 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.network.p2p.seed;
import bisq.network.p2p.NodeAddress;
import bisq.common.config.Config;
import org.junit.Assert;
import org.junit.Test;
import static java.lang.String.format;
public class DefaultSeedNodeRepositoryTest {
@Test
public void getSeedNodes() {
DefaultSeedNodeRepository DUT = new DefaultSeedNodeRepository(new Config());
Assert.assertFalse(DUT.getSeedNodeAddresses().isEmpty());
}
@Test
public void manualSeedNodes() {
String seed1 = "asdf:8001";
String seed2 = "fdsa:6001";
String seedNodesOption = format("--%s=%s,%s", Config.SEED_NODES, seed1, seed2);
DefaultSeedNodeRepository DUT = new DefaultSeedNodeRepository(new Config(seedNodesOption));
Assert.assertFalse(DUT.getSeedNodeAddresses().isEmpty());
Assert.assertEquals(2, DUT.getSeedNodeAddresses().size());
Assert.assertTrue(DUT.getSeedNodeAddresses().contains(new NodeAddress(seed1)));
Assert.assertTrue(DUT.getSeedNodeAddresses().contains(new NodeAddress(seed2)));
}
}

View file

@ -0,0 +1,90 @@
/*
* 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.notifications;
import bisq.common.util.Tuple2;
import java.util.Arrays;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@Slf4j
public class MobileModelTest {
@Test
public void testParseDescriptor() {
MobileModel mobileModel = new MobileModel();
List<Tuple2<String, Boolean>> list = Arrays.asList(
new Tuple2<>("iPod Touch 5", false),
new Tuple2<>("iPod Touch 6", false),
new Tuple2<>("iPhone 4", false),
new Tuple2<>("iPhone 4s", false),
new Tuple2<>("iPhone 5", false),
new Tuple2<>("iPhone 5c", false),
new Tuple2<>("iPhone 5s", false),
new Tuple2<>("iPhone 6", false),
new Tuple2<>("iPhone 6 Plus", false),
new Tuple2<>("iPhone 6s", true),
new Tuple2<>("iPhone 6s Plus", true),
new Tuple2<>("iPhone 7", true),
new Tuple2<>("iPhone 7 Plus", true),
new Tuple2<>("iPhone SE", false), // unclear
new Tuple2<>("iPhone 8", true),
new Tuple2<>("iPhone 8 Plus", true),
new Tuple2<>("iPhone X", true),
new Tuple2<>("iPhone XS", true),
new Tuple2<>("iPhone XS Max", true),
new Tuple2<>("iPhone XR", true),
new Tuple2<>("iPhone 11", true),
new Tuple2<>("iPhone 11 Pro", true),
new Tuple2<>("iPhone 11 Pro Max", true),
new Tuple2<>("iPhone 11S", true), // not sure if this model will exist, but based on past versioning it is possible
// need to ensure it will be parsed correctly just in case
new Tuple2<>("iPad 2", false),
new Tuple2<>("iPad 3", false),
new Tuple2<>("iPad 4", false),
new Tuple2<>("iPad Air", false),
new Tuple2<>("iPad Air 2", false),
new Tuple2<>("iPad 5", false),
new Tuple2<>("iPad 6", false),
new Tuple2<>("iPad Mini", false),
new Tuple2<>("iPad Mini 2", false),
new Tuple2<>("iPad Mini 3", false),
new Tuple2<>("iPad Mini 4", false),
new Tuple2<>("iPad Pro 9.7 Inch", true),
new Tuple2<>("iPad Pro 12.9 Inch", true),
new Tuple2<>("iPad Pro 12.9 Inch 2. Generation", true),
new Tuple2<>("iPad Pro 10.5 Inch", true)
);
list.forEach(tuple -> {
log.info(tuple.toString());
assertEquals("tuple: " + tuple, mobileModel.parseDescriptor(tuple.first), tuple.second);
});
}
}

View file

@ -0,0 +1,79 @@
/*
* 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.offer;
import com.natpryce.makeiteasy.Instantiator;
import com.natpryce.makeiteasy.Maker;
import com.natpryce.makeiteasy.Property;
import static com.natpryce.makeiteasy.MakeItEasy.a;
public class OfferMaker {
public static final Property<Offer, Long> price = new Property<>();
public static final Property<Offer, Long> minAmount = new Property<>();
public static final Property<Offer, Long> amount = new Property<>();
public static final Property<Offer, String> baseCurrencyCode = new Property<>();
public static final Property<Offer, String> counterCurrencyCode = new Property<>();
public static final Property<Offer, OfferPayload.Direction> direction = new Property<>();
public static final Property<Offer, Boolean> useMarketBasedPrice = new Property<>();
public static final Property<Offer, Double> marketPriceMargin = new Property<>();
public static final Property<Offer, String> id = new Property<>();
public static final Instantiator<Offer> Offer = lookup -> new Offer(
new OfferPayload(lookup.valueOf(id, "1234"),
0L,
null,
null,
lookup.valueOf(direction, OfferPayload.Direction.BUY),
lookup.valueOf(price, 100000L),
lookup.valueOf(marketPriceMargin, 0.0),
lookup.valueOf(useMarketBasedPrice, false),
lookup.valueOf(amount, 100000L),
lookup.valueOf(minAmount, 100000L),
lookup.valueOf(baseCurrencyCode, "BTC"),
lookup.valueOf(counterCurrencyCode, "USD"),
null,
null,
"SEPA",
"",
null,
null,
null,
null,
null,
"",
0L,
0L,
0L,
false,
0L,
0L,
0L,
0L,
false,
false,
0L,
0L,
false,
null,
null,
0));
public static final Maker<Offer> btcUsdOffer = a(Offer);
}

View file

@ -0,0 +1,48 @@
/*
* 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.offer;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class OfferTest {
@Test
public void testHasNoRange() {
OfferPayload payload = mock(OfferPayload.class);
when(payload.getMinAmount()).thenReturn(1000L);
when(payload.getAmount()).thenReturn(1000L);
Offer offer = new Offer(payload);
assertFalse(offer.isRange());
}
@Test
public void testHasRange() {
OfferPayload payload = mock(OfferPayload.class);
when(payload.getMinAmount()).thenReturn(1000L);
when(payload.getAmount()).thenReturn(2000L);
Offer offer = new Offer(payload);
assertTrue(offer.isRange());
}
}

View file

@ -0,0 +1,176 @@
package bisq.core.offer;
import bisq.core.api.CoreContext;
import bisq.core.trade.TradableList;
import bisq.network.p2p.P2PService;
import bisq.network.p2p.peers.PeerManager;
import bisq.common.file.CorruptedStorageFileHandler;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import bisq.common.persistence.PersistenceManager;
import java.nio.file.Files;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static bisq.core.offer.OfferMaker.btcUsdOffer;
import static com.natpryce.makeiteasy.MakeItEasy.make;
import static junit.framework.TestCase.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
public class OpenOfferManagerTest {
private PersistenceManager<TradableList<OpenOffer>> persistenceManager;
private CoreContext coreContext;
@Before
public void setUp() throws Exception {
var corruptedStorageFileHandler = mock(CorruptedStorageFileHandler.class);
var storageDir = Files.createTempDirectory("storage").toFile();
persistenceManager = new PersistenceManager<>(storageDir, null, corruptedStorageFileHandler);
coreContext = new CoreContext();
}
@After
public void tearDown() {
persistenceManager.shutdown();
}
@Test
public void testStartEditOfferForActiveOffer() {
P2PService p2PService = mock(P2PService.class);
OfferBookService offerBookService = mock(OfferBookService.class);
when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class));
final OpenOfferManager manager = new OpenOfferManager(coreContext,
null,
null,
null,
p2PService,
null,
null,
null,
offerBookService,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
persistenceManager);
AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false);
doAnswer(invocation -> {
((ResultHandler) invocation.getArgument(1)).handleResult();
return null;
}).when(offerBookService).deactivateOffer(any(OfferPayload.class), any(ResultHandler.class), any(ErrorMessageHandler.class));
final OpenOffer openOffer = new OpenOffer(make(btcUsdOffer));
ResultHandler resultHandler = () -> startEditOfferSuccessful.set(true);
manager.editOpenOfferStart(openOffer, resultHandler, null);
verify(offerBookService, times(1)).deactivateOffer(any(OfferPayload.class), any(ResultHandler.class), any(ErrorMessageHandler.class));
assertTrue(startEditOfferSuccessful.get());
}
@Test
public void testStartEditOfferForDeactivatedOffer() {
P2PService p2PService = mock(P2PService.class);
OfferBookService offerBookService = mock(OfferBookService.class);
when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class));
final OpenOfferManager manager = new OpenOfferManager(coreContext,
null,
null,
null,
p2PService,
null,
null,
null,
offerBookService,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
persistenceManager);
AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false);
ResultHandler resultHandler = () -> startEditOfferSuccessful.set(true);
final OpenOffer openOffer = new OpenOffer(make(btcUsdOffer));
openOffer.setState(OpenOffer.State.DEACTIVATED);
manager.editOpenOfferStart(openOffer, resultHandler, null);
assertTrue(startEditOfferSuccessful.get());
}
@Test
public void testStartEditOfferForOfferThatIsCurrentlyEdited() {
P2PService p2PService = mock(P2PService.class);
OfferBookService offerBookService = mock(OfferBookService.class);
when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class));
final OpenOfferManager manager = new OpenOfferManager(coreContext,
null,
null,
null,
p2PService,
null,
null,
null,
offerBookService,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
persistenceManager);
AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false);
ResultHandler resultHandler = () -> startEditOfferSuccessful.set(true);
final OpenOffer openOffer = new OpenOffer(make(btcUsdOffer));
openOffer.setState(OpenOffer.State.DEACTIVATED);
manager.editOpenOfferStart(openOffer, resultHandler, null);
assertTrue(startEditOfferSuccessful.get());
startEditOfferSuccessful.set(false);
manager.editOpenOfferStart(openOffer, resultHandler, null);
assertTrue(startEditOfferSuccessful.get());
}
}

View file

@ -0,0 +1,85 @@
/*
* 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.offer.availability;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ArbitratorSelectionTest {
@Test
public void testGetLeastUsedArbitrator() {
// We get least used selected
List<String> lastAddressesUsedInTrades;
Set<String> arbitrators;
String result;
lastAddressesUsedInTrades = Arrays.asList("arb1", "arb2", "arb1");
arbitrators = new HashSet<>(Arrays.asList("arb1", "arb2"));
result = DisputeAgentSelection.getLeastUsedDisputeAgent(lastAddressesUsedInTrades, arbitrators);
assertEquals("arb2", result);
// if all are same we use first according to alphanumeric sorting
lastAddressesUsedInTrades = Arrays.asList("arb1", "arb2", "arb3");
arbitrators = new HashSet<>(Arrays.asList("arb1", "arb2", "arb3"));
result = DisputeAgentSelection.getLeastUsedDisputeAgent(lastAddressesUsedInTrades, arbitrators);
assertEquals("arb1", result);
lastAddressesUsedInTrades = Arrays.asList("arb1", "arb2", "arb3", "arb1");
arbitrators = new HashSet<>(Arrays.asList("arb1", "arb2", "arb3"));
result = DisputeAgentSelection.getLeastUsedDisputeAgent(lastAddressesUsedInTrades, arbitrators);
assertEquals("arb2", result);
lastAddressesUsedInTrades = Arrays.asList("arb1", "arb2", "arb3", "arb1", "arb2");
arbitrators = new HashSet<>(Arrays.asList("arb1", "arb2", "arb3"));
result = DisputeAgentSelection.getLeastUsedDisputeAgent(lastAddressesUsedInTrades, arbitrators);
assertEquals("arb3", result);
lastAddressesUsedInTrades = Arrays.asList("xxx", "ccc", "aaa");
arbitrators = new HashSet<>(Arrays.asList("aaa", "ccc", "xxx"));
result = DisputeAgentSelection.getLeastUsedDisputeAgent(lastAddressesUsedInTrades, arbitrators);
assertEquals("aaa", result);
lastAddressesUsedInTrades = Arrays.asList("333", "000", "111");
arbitrators = new HashSet<>(Arrays.asList("111", "333", "000"));
result = DisputeAgentSelection.getLeastUsedDisputeAgent(lastAddressesUsedInTrades, arbitrators);
assertEquals("000", result);
// if winner is not in our arb list we use our arb from arbitrators even if never used in trades
lastAddressesUsedInTrades = Arrays.asList("arb1", "arb2", "arb3");
arbitrators = new HashSet<>(Arrays.asList("arb4"));
result = DisputeAgentSelection.getLeastUsedDisputeAgent(lastAddressesUsedInTrades, arbitrators);
assertEquals("arb4", result);
// if winner (arb2) is not in our arb list we use our arb from arbitrators
lastAddressesUsedInTrades = Arrays.asList("arb1", "arb1", "arb1", "arb2");
arbitrators = new HashSet<>(Arrays.asList("arb1"));
result = DisputeAgentSelection.getLeastUsedDisputeAgent(lastAddressesUsedInTrades, arbitrators);
assertEquals("arb1", result);
// arb1 is used least
lastAddressesUsedInTrades = Arrays.asList("arb1", "arb2", "arb2", "arb2", "arb1", "arb1", "arb2");
arbitrators = new HashSet<>(Arrays.asList("arb1", "arb2"));
result = DisputeAgentSelection.getLeastUsedDisputeAgent(lastAddressesUsedInTrades, arbitrators);
assertEquals("arb1", result);
}
}

View file

@ -0,0 +1,74 @@
/*
* 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.payment;
import bisq.core.account.witness.AccountAgeWitness;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.offer.Offer;
import bisq.core.payment.payload.PaymentAccountPayload;
import java.util.Collections;
import org.junit.Test;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class PaymentAccountsTest {
@Test
public void testGetOldestPaymentAccountForOfferWhenNoValidAccounts() {
PaymentAccounts accounts = new PaymentAccounts(Collections.emptySet(), mock(AccountAgeWitnessService.class));
PaymentAccount actual = accounts.getOldestPaymentAccountForOffer(mock(Offer.class));
assertNull(actual);
}
// @Test
// public void testGetOldestPaymentAccountForOffer() {
// AccountAgeWitnessService service = mock(AccountAgeWitnessService.class);
//
// PaymentAccount oldest = createAccountWithAge(service, 3);
// Set<PaymentAccount> accounts = Sets.newHashSet(
// oldest,
// createAccountWithAge(service, 2),
// createAccountWithAge(service, 1));
//
// BiFunction<Offer, PaymentAccount, Boolean> dummyValidator = (offer, account) -> true;
// PaymentAccounts testedEntity = new PaymentAccounts(accounts, service, dummyValidator);
//
// PaymentAccount actual = testedEntity.getOldestPaymentAccountForOffer(mock(Offer.class));
// assertEquals(oldest, actual);
// }
private static PaymentAccount createAccountWithAge(AccountAgeWitnessService service, long age) {
PaymentAccountPayload payload = mock(PaymentAccountPayload.class);
PaymentAccount account = mock(PaymentAccount.class);
when(account.getPaymentAccountPayload()).thenReturn(payload);
AccountAgeWitness witness = mock(AccountAgeWitness.class);
when(service.getAccountAge(eq(witness), any())).thenReturn(age);
when(service.getMyWitness(payload)).thenReturn(witness);
return account;
}
}

View file

@ -0,0 +1,99 @@
/*
* 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.payment;
import bisq.core.locale.CryptoCurrency;
import bisq.core.offer.Offer;
import bisq.core.payment.payload.PaymentMethod;
import com.google.common.collect.Lists;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ReceiptPredicatesTest {
private final ReceiptPredicates predicates = new ReceiptPredicates();
@Test
public void testIsMatchingCurrency() {
Offer offer = mock(Offer.class);
when(offer.getCurrencyCode()).thenReturn("USD");
PaymentAccount account = mock(PaymentAccount.class);
when(account.getTradeCurrencies()).thenReturn(Lists.newArrayList(
new CryptoCurrency("BTC", "Bitcoin"),
new CryptoCurrency("ETH", "Ether")));
assertFalse(predicates.isMatchingCurrency(offer, account));
}
@Test
public void testIsMatchingSepaOffer() {
Offer offer = mock(Offer.class);
PaymentMethod.SEPA = mock(PaymentMethod.class);
when(offer.getPaymentMethod()).thenReturn(PaymentMethod.SEPA);
assertTrue(predicates.isMatchingSepaOffer(offer, mock(SepaInstantAccount.class)));
assertTrue(predicates.isMatchingSepaOffer(offer, mock(SepaAccount.class)));
}
@Test
public void testIsMatchingSepaInstant() {
Offer offer = mock(Offer.class);
PaymentMethod.SEPA_INSTANT = mock(PaymentMethod.class);
when(offer.getPaymentMethod()).thenReturn(PaymentMethod.SEPA_INSTANT);
assertTrue(predicates.isMatchingSepaInstant(offer, mock(SepaInstantAccount.class)));
assertFalse(predicates.isMatchingSepaInstant(offer, mock(SepaAccount.class)));
}
@Test
public void testIsMatchingCountryCodes() {
CountryBasedPaymentAccount account = mock(CountryBasedPaymentAccount.class);
when(account.getCountry()).thenReturn(null);
assertFalse(predicates.isMatchingCountryCodes(mock(Offer.class), account));
}
@Test
public void testIsSameOrSpecificBank() {
PaymentMethod.SAME_BANK = mock(PaymentMethod.class);
Offer offer = mock(Offer.class);
when(offer.getPaymentMethod()).thenReturn(PaymentMethod.SAME_BANK);
assertTrue(predicates.isOfferRequireSameOrSpecificBank(offer, mock(NationalBankAccount.class)));
}
@Test
public void testIsEqualPaymentMethods() {
PaymentMethod method = PaymentMethod.getDummyPaymentMethod("1");
Offer offer = mock(Offer.class);
when(offer.getPaymentMethod()).thenReturn(method);
PaymentAccount account = mock(PaymentAccount.class);
when(account.getPaymentMethod()).thenReturn(method);
assertTrue(predicates.isEqualPaymentMethods(offer, account));
}
}

View file

@ -0,0 +1,267 @@
/*
* 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.payment;
import bisq.core.offer.Offer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class ReceiptValidatorTest {
private ReceiptValidator validator;
private PaymentAccount account;
private Offer offer;
private ReceiptPredicates predicates;
@Before
public void setUp() {
this.predicates = mock(ReceiptPredicates.class);
this.account = mock(CountryBasedPaymentAccount.class);
this.offer = mock(Offer.class);
this.validator = new ReceiptValidator(offer, account, predicates);
}
@After
public void tearDown() {
verifyZeroInteractions(offer);
}
@Test
public void testIsValidWhenCurrencyDoesNotMatch() {
when(predicates.isMatchingCurrency(offer, account)).thenReturn(false);
assertFalse(validator.isValid());
verify(predicates).isMatchingCurrency(offer, account);
}
@Test
public void testIsValidWhenNotCountryBasedAccount() {
account = mock(PaymentAccount.class);
assertFalse(account instanceof CountryBasedPaymentAccount);
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(true);
assertTrue(new ReceiptValidator(offer, account, predicates).isValid());
}
@Test
public void testIsValidWhenNotMatchingCodes() {
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(false);
assertFalse(validator.isValid());
verify(predicates).isMatchingCountryCodes(offer, account);
}
@Test
public void testIsValidWhenSepaOffer() {
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(false);
when(predicates.isMatchingSepaOffer(offer, account)).thenReturn(true);
assertTrue(validator.isValid());
verify(predicates).isMatchingSepaOffer(offer, account);
}
@Test
public void testIsValidWhenSepaInstant() {
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(false);
when(predicates.isMatchingSepaInstant(offer, account)).thenReturn(true);
assertTrue(validator.isValid());
verify(predicates).isMatchingSepaOffer(offer, account);
}
@Test
public void testIsValidWhenSpecificBankAccountAndOfferRequireSpecificBank() {
account = mock(SpecificBanksAccount.class);
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isMatchingSepaOffer(offer, account)).thenReturn(false);
when(predicates.isMatchingSepaInstant(offer, account)).thenReturn(false);
when(predicates.isOfferRequireSameOrSpecificBank(offer, account)).thenReturn(true);
when(predicates.isMatchingBankId(offer, account)).thenReturn(false);
assertFalse(new ReceiptValidator(offer, account, predicates).isValid());
}
@Test
public void testIsValidWhenSameBankAccountAndOfferRequireSpecificBank() {
account = mock(SameBankAccount.class);
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isMatchingSepaOffer(offer, account)).thenReturn(false);
when(predicates.isMatchingSepaInstant(offer, account)).thenReturn(false);
when(predicates.isOfferRequireSameOrSpecificBank(offer, account)).thenReturn(true);
when(predicates.isMatchingBankId(offer, account)).thenReturn(false);
assertFalse(new ReceiptValidator(offer, account, predicates).isValid());
}
@Test
public void testIsValidWhenSpecificBankAccount() {
account = mock(SpecificBanksAccount.class);
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isMatchingSepaOffer(offer, account)).thenReturn(false);
when(predicates.isMatchingSepaInstant(offer, account)).thenReturn(false);
when(predicates.isOfferRequireSameOrSpecificBank(offer, account)).thenReturn(true);
when(predicates.isMatchingBankId(offer, account)).thenReturn(true);
assertTrue(new ReceiptValidator(offer, account, predicates).isValid());
}
@Test
public void testIsValidWhenSameBankAccount() {
account = mock(SameBankAccount.class);
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isMatchingSepaOffer(offer, account)).thenReturn(false);
when(predicates.isMatchingSepaInstant(offer, account)).thenReturn(false);
when(predicates.isOfferRequireSameOrSpecificBank(offer, account)).thenReturn(true);
when(predicates.isMatchingBankId(offer, account)).thenReturn(true);
assertTrue(new ReceiptValidator(offer, account, predicates).isValid());
}
@Test
public void testIsValidWhenNationalBankAccount() {
account = mock(NationalBankAccount.class);
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isMatchingSepaOffer(offer, account)).thenReturn(false);
when(predicates.isMatchingSepaInstant(offer, account)).thenReturn(false);
when(predicates.isOfferRequireSameOrSpecificBank(offer, account)).thenReturn(false);
assertTrue(new ReceiptValidator(offer, account, predicates).isValid());
}
@Test
// Same or Specific Bank offers can't be taken by National Bank accounts. TODO: Consider partially relaxing to allow Specific Banks.
public void testIsValidWhenNationalBankAccountAndOfferIsNot() {
account = mock(NationalBankAccount.class);
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(false);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isMatchingSepaOffer(offer, account)).thenReturn(false);
when(predicates.isMatchingSepaInstant(offer, account)).thenReturn(false);
assertFalse(new ReceiptValidator(offer, account, predicates).isValid());
verify(predicates, never()).isOfferRequireSameOrSpecificBank(offer, account);
verify(predicates, never()).isMatchingBankId(offer, account);
}
@Test
// National or Same Bank offers can't be taken by Specific Banks accounts. TODO: Consider partially relaxing to allow National Bank.
public void testIsValidWhenSpecificBanksAccountAndOfferIsNot() {
account = mock(SpecificBanksAccount.class);
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(false);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isMatchingSepaOffer(offer, account)).thenReturn(false);
when(predicates.isMatchingSepaInstant(offer, account)).thenReturn(false);
assertFalse(new ReceiptValidator(offer, account, predicates).isValid());
verify(predicates, never()).isOfferRequireSameOrSpecificBank(offer, account);
verify(predicates, never()).isMatchingBankId(offer, account);
}
@Test
// National or Specific Bank offers can't be taken by Same Bank accounts.
public void testIsValidWhenSameBankAccountAndOfferIsNot() {
account = mock(SameBankAccount.class);
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(false);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isMatchingSepaOffer(offer, account)).thenReturn(false);
when(predicates.isMatchingSepaInstant(offer, account)).thenReturn(false);
assertFalse(new ReceiptValidator(offer, account, predicates).isValid());
verify(predicates, never()).isOfferRequireSameOrSpecificBank(offer, account);
verify(predicates, never()).isMatchingBankId(offer, account);
}
@Test
public void testIsValidWhenWesternUnionAccount() {
account = mock(WesternUnionAccount.class);
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isMatchingSepaOffer(offer, account)).thenReturn(false);
when(predicates.isMatchingSepaInstant(offer, account)).thenReturn(false);
when(predicates.isOfferRequireSameOrSpecificBank(offer, account)).thenReturn(false);
assertTrue(new ReceiptValidator(offer, account, predicates).isValid());
}
@Test
public void testIsValidWhenWesternIrregularAccount() {
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isMatchingSepaOffer(offer, account)).thenReturn(false);
when(predicates.isMatchingSepaInstant(offer, account)).thenReturn(false);
when(predicates.isOfferRequireSameOrSpecificBank(offer, account)).thenReturn(false);
assertTrue(validator.isValid());
}
@Test
public void testIsValidWhenMoneyGramAccount() {
account = mock(MoneyGramAccount.class);
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(true);
assertTrue(new ReceiptValidator(offer, account, predicates).isValid());
verify(predicates, never()).isMatchingCountryCodes(offer, account);
verify(predicates, never()).isMatchingSepaOffer(offer, account);
verify(predicates, never()).isMatchingSepaInstant(offer, account);
verify(predicates, never()).isOfferRequireSameOrSpecificBank(offer, account);
}
}

View file

@ -0,0 +1,50 @@
/*
* 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.payment;
import bisq.core.dao.governance.period.PeriodService;
import bisq.core.dao.state.DaoStateService;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
public class TradeLimitsTest {
@Test
public void testGetFirstMonthRiskBasedTradeLimit() {
TradeLimits tradeLimits = new TradeLimits(mock(DaoStateService.class), mock(PeriodService.class));
long expected, result;
expected = 0;
result = tradeLimits.getFirstMonthRiskBasedTradeLimit(0, 1);
assertEquals(expected, result);
expected = 25000000;
result = tradeLimits.getFirstMonthRiskBasedTradeLimit(100000000, 1);
assertEquals(expected, result);
expected = 3130000; //0.03125 -> 0.0313 -> 0.0313
result = tradeLimits.getFirstMonthRiskBasedTradeLimit(100000000, 8);
assertEquals(expected, result);
expected = 6250000;
result = tradeLimits.getFirstMonthRiskBasedTradeLimit(200000000, 8);
assertEquals(expected, result);
}
}

View file

@ -0,0 +1,55 @@
/*
* 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.payment.validation;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.asset.AssetRegistry;
import bisq.common.config.BaseCurrencyNetwork;
import bisq.common.config.Config;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class AltCoinAddressValidatorTest {
@Test
public void test() {
AltCoinAddressValidator validator = new AltCoinAddressValidator(new AssetRegistry());
BaseCurrencyNetwork baseCurrencyNetwork = Config.baseCurrencyNetwork();
String currencyCode = baseCurrencyNetwork.getCurrencyCode();
Res.setBaseCurrencyCode(currencyCode);
Res.setBaseCurrencyName(baseCurrencyNetwork.getCurrencyName());
CurrencyUtil.setBaseCurrencyCode(currencyCode);
validator.setCurrencyCode("BTC");
assertTrue(validator.validate("17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem").isValid);
validator.setCurrencyCode("LTC");
assertTrue(validator.validate("Lg3PX8wRWmApFCoCMAsPF5P9dPHYQHEWKW").isValid);
validator.setCurrencyCode("BOGUS");
assertFalse(validator.validate("1BOGUSADDR").isValid);
}
}

View file

@ -0,0 +1,280 @@
/*
* 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.provider.mempool;
import bisq.core.dao.governance.param.Param;
import bisq.core.dao.state.DaoStateService;
import bisq.core.util.ParsingUtils;
import bisq.core.util.coin.BsqFormatter;
import com.google.gson.Gson;
import org.apache.commons.io.IOUtils;
import org.bitcoinj.core.Coin;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import org.junit.Test;
import org.junit.Assert;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class TxValidatorTest {
private static final Logger log = LoggerFactory.getLogger(TxValidatorTest.class);
private List<String> btcFeeReceivers = new ArrayList<>();
public TxValidatorTest() {
btcFeeReceivers.add("1EKXx73oUhHaUh8JBimtiPGgHfwNmxYKAj");
btcFeeReceivers.add("1HpvvMHcoXQsX85CjTsco5ZAAMoGu2Mze9");
btcFeeReceivers.add("3EfRGckBQQuk7cpU7SwatPv8kFD1vALkTU");
btcFeeReceivers.add("13sxMq8mTw7CTSqgGiMPfwo6ZDsVYrHLmR");
btcFeeReceivers.add("19qA2BVPoyXDfHKVMovKG7SoxGY7xrBV8c");
btcFeeReceivers.add("19BNi5EpZhgBBWAt5ka7xWpJpX2ZWJEYyq");
btcFeeReceivers.add("38bZBj5peYS3Husdz7AH3gEUiUbYRD951t");
btcFeeReceivers.add("3EtUWqsGThPtjwUczw27YCo6EWvQdaPUyp");
btcFeeReceivers.add("1BVxNn3T12veSK6DgqwU4Hdn7QHcDDRag7");
btcFeeReceivers.add("3A8Zc1XioE2HRzYfbb5P8iemCS72M6vRJV");
btcFeeReceivers.add("34VLFgtFKAtwTdZ5rengTT2g2zC99sWQLC");
log.warn("Known BTC fee receivers: {}", btcFeeReceivers.toString());
}
@Test
public void testMakerTx() throws InterruptedException {
String mempoolData, offerData;
// paid the correct amount of BSQ fees
offerData = "msimscqb,0636bafb14890edfb95465e66e2b1e15915f7fb595f9b653b9129c15ef4c1c4b,1000000,10,0,662390";
mempoolData = "{\"txid\":\"0636bafb14890edfb95465e66e2b1e15915f7fb595f9b653b9129c15ef4c1c4b\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":7899}},{\"vout\":2,\"prevout\":{\"value\":54877439}}],\"vout\":[{\"scriptpubkey_address\":\"1FCUu7hqKCSsGhVJaLbGEoCWdZRJRNqq8w\",\"value\":7889},{\"scriptpubkey_address\":\"bc1qkj5l4wxl00ufdx6ygcnrck9fz5u927gkwqcgey\",\"value\":1600000},{\"scriptpubkey_address\":\"bc1qkw4a8u9l5w9fhdh3ue9v7e7celk4jyudzg5gk5\",\"value\":53276799}],\"size\":405,\"weight\":1287,\"fee\":650,\"status\":{\"confirmed\":true,\"block_height\":663140}}";
Assert.assertTrue(createTxValidator(offerData).parseJsonValidateMakerFeeTx(mempoolData, btcFeeReceivers).getResult());
// UNDERPAID expected 1.01 BSQ, actual fee paid 0.80 BSQ (USED 8.00 RATE INSTEAD OF 10.06 RATE)
// PASS due to leniency rule of accepting old DAO rate parameters: https://github.com/bisq-network/bisq/issues/5329#issuecomment-803223859
offerData = "48067552,3b6009da764b71d79a4df8e2d8960b6919cae2e9bdccd5ef281e261fa9cd31b3,10000000,80,0,667656";
mempoolData = "{\"txid\":\"3b6009da764b71d79a4df8e2d8960b6919cae2e9bdccd5ef281e261fa9cd31b3\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":9717}},{\"vout\":0,\"prevout\":{\"value\":4434912}},{\"vout\":2,\"prevout\":{\"value\":12809932}}],\"vout\":[{\"scriptpubkey_address\":\"1Nzqa4J7ck5bgz7QNXKtcjZExAvReozFo4\",\"value\":9637},{\"scriptpubkey_address\":\"bc1qhmmulf5prreqhccqy2wqpxxn6dcye7ame9dd57\",\"value\":11500000},{\"scriptpubkey_address\":\"bc1qx6hg8km2jdjc5ukhuedmkseu9wlsjtd8zeajpj\",\"value\":5721894}],\"size\":553,\"weight\":1879,\"fee\":23030,\"status\":{\"confirmed\":true,\"block_height\":667660}}";
Assert.assertTrue(createTxValidator(offerData).parseJsonValidateMakerFeeTx(mempoolData, btcFeeReceivers).getResult());
// UNDERPAID Expected fee: 0.61 BSQ, actual fee paid: 0.35 BSQ (USED 5.75 RATE INSTEAD OF 10.06 RATE)
// PASS due to leniency rule of accepting old DAO rate parameters: https://github.com/bisq-network/bisq/issues/5329#issuecomment-803223859
offerData = "am7DzIv,4cdea8872a7d96210f378e0221dc1aae8ee9abb282582afa7546890fb39b7189,6100000,35,0,668195";
mempoolData = "{\"txid\":\"4cdea8872a7d96210f378e0221dc1aae8ee9abb282582afa7546890fb39b7189\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":23893}},{\"vout\":1,\"prevout\":{\"value\":1440000}},{\"vout\":2,\"prevout\":{\"value\":16390881}}],\"vout\":[{\"scriptpubkey_address\":\"1Kmrzq3WGCQsZw5kroEphuk1KgsEr65yB7\",\"value\":23858},{\"scriptpubkey_address\":\"bc1qyw5qql9m7rkse9mhcun225nrjpwycszsa5dpjg\",\"value\":7015000},{\"scriptpubkey_address\":\"bc1q90y3p6mg0pe3rvvzfeudq4mfxafgpc9rulruff\",\"value\":10774186}],\"size\":554,\"weight\":1559,\"fee\":41730,\"status\":{\"confirmed\":true,\"block_height\":668198}}";
Assert.assertTrue(createTxValidator(offerData).parseJsonValidateMakerFeeTx(mempoolData, btcFeeReceivers).getResult());
// UNDERPAID expected 0.11 BSQ, actual fee paid 0.08 BSQ (USED 5.75 RATE INSTEAD OF 7.53)
// PASS due to leniency rule of accepting old DAO rate parameters: https://github.com/bisq-network/bisq/issues/5329#issuecomment-803223859
offerData = "F1dzaFNQ,f72e263947c9dee6fbe7093fc85be34a149ef5bcfdd49b59b9cc3322fea8967b,1440000,8,0,670822, bsq paid too little";
mempoolData = "{\"txid\":\"f72e263947c9dee6fbe7093fc85be34a149ef5bcfdd49b59b9cc3322fea8967b\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":15163}},{\"vout\":2,\"prevout\":{\"value\":6100000}}],\"vout\":[{\"scriptpubkey_address\":\"1MEsc2m4MSomNJWSr1p6fhnUQMyA3DRGrN\",\"value\":15155},{\"scriptpubkey_address\":\"bc1qztgwe9ry9a9puchjuscqdnv4v9lsm2ut0jtfec\",\"value\":2040000},{\"scriptpubkey_address\":\"bc1q0nstwxc0vqkj4x000xt328mfjapvlsd56nn70h\",\"value\":4048308}],\"size\":406,\"weight\":1291,\"fee\":11700,\"status\":{\"confirmed\":true,\"block_height\":670823}}";
Assert.assertTrue(createTxValidator(offerData).parseJsonValidateMakerFeeTx(mempoolData, btcFeeReceivers).getResult());
}
@Test
public void testTakerTx() throws InterruptedException {
String mempoolData, offerData;
// The fee was more than what we expected: Expected BTC fee: 5000 sats , actual fee paid: 6000 sats
offerData = "00072328,3524364062c96ba0280621309e8b539d152154422294c2cf263a965dcde9a8ca,1000000,6000,1,614672";
mempoolData = "{\"txid\":\"3524364062c96ba0280621309e8b539d152154422294c2cf263a965dcde9a8ca\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":1,\"prevout\":{\"value\":2971000}}],\"vout\":[{\"scriptpubkey_address\":\"3A8Zc1XioE2HRzYfbb5P8iemCS72M6vRJV\",\"value\":6000},{\"scriptpubkey_address\":\"1Hxu2X9Nr2fT3qEk9yjhiF54TJEz1Cxjoa\",\"value\":1607600},{\"scriptpubkey_address\":\"16VP6nHDDkmCMwaJj4PeyVHB88heDdVu9e\",\"value\":1353600}],\"size\":257,\"weight\":1028,\"fee\":3800,\"status\":{\"confirmed\":true,\"block_height\":614672}}";
Assert.assertTrue(createTxValidator(offerData).parseJsonValidateTakerFeeTx(mempoolData, btcFeeReceivers).getResult());
// The fee matched what we expected
offerData = "00072328,12f658954890d38ce698355be0b27fdd68d092c7b1b7475381918db060f46166,6250000,188,0,615955";
mempoolData = "{\"txid\":\"12f658954890d38ce698355be0b27fdd68d092c7b1b7475381918db060f46166\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":19980}},{\"vout\":2,\"prevout\":{\"value\":2086015}},{\"vout\":0,\"prevout\":{\"value\":1100000}},{\"vout\":2,\"prevout\":{\"value\":938200}}],\"vout\":[{\"scriptpubkey_address\":\"17qiF1TYgT1YvsCPJyXQoKMtBZ7YJBW9GH\",\"value\":19792},{\"scriptpubkey_address\":\"16aFKD5hvEjJgPme5yRNJT2rAPdTXzdQc2\",\"value\":3768432},{\"scriptpubkey_address\":\"1D5V3QW8f5n4PhwfPgNkW9eWZwNJFyVU8n\",\"value\":346755}],\"size\":701,\"weight\":2804,\"fee\":9216,\"status\":{\"confirmed\":true,\"block_height\":615955}}";
Assert.assertTrue(createTxValidator(offerData).parseJsonValidateTakerFeeTx(mempoolData, btcFeeReceivers).getResult());
// The fee was more than what we expected: Expected BTC fee: 5000 sats , actual fee paid: 7000 sats
offerData = "bsqtrade,dfa4555ab78c657cad073e3f29c38c563d9dafc53afaa8c6af28510c734305c4,1000000,10,1,662390";
mempoolData = "{\"txid\":\"dfa4555ab78c657cad073e3f29c38c563d9dafc53afaa8c6af28510c734305c4\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":678997}}],\"vout\":[{\"scriptpubkey_address\":\"3EfRGckBQQuk7cpU7SwatPv8kFD1vALkTU\",\"value\":7000},{\"scriptpubkey_address\":\"bc1qu6vey3e7flzg8gmhun05m43uc2vz0ay33kuu6r\",\"value\":647998}],\"size\":224,\"weight\":566,\"fee\":23999,\"status\":{\"confirmed\":true,\"block_height\":669720}}";
Assert.assertTrue(createTxValidator(offerData).parseJsonValidateTakerFeeTx(mempoolData, btcFeeReceivers).getResult());
// The fee matched what we expected
offerData = "89284,e1269aad63b3d894f5133ad658960971ef5c0fce6a13ad10544dc50fa3360588,900000,9,0,666473";
mempoolData = "{\"txid\":\"e1269aad63b3d894f5133ad658960971ef5c0fce6a13ad10544dc50fa3360588\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":72738}},{\"vout\":0,\"prevout\":{\"value\":1600000}}],\"vout\":[{\"scriptpubkey_address\":\"17Kh5Ype9yNomqRrqu2k1mdV5c6FcKfGwQ\",\"value\":72691},{\"scriptpubkey_address\":\"bc1qdr9zcw7gf2sehxkux4fmqujm5uguhaqz7l9lca\",\"value\":629016},{\"scriptpubkey_address\":\"bc1qgqrrqv8q6l5d3t52fe28ghuhz4xqrsyxlwn03z\",\"value\":956523}],\"size\":404,\"weight\":1286,\"fee\":14508,\"status\":{\"confirmed\":true,\"block_height\":672388}}";
Assert.assertTrue(createTxValidator(offerData).parseJsonValidateTakerFeeTx(mempoolData, btcFeeReceivers).getResult());
// UNDERPAID: Expected fee: 7.04 BSQ, actual fee paid: 1.01 BSQ
offerData = "VOxRS,e99ea06aefc824fd45031447f7a0b56efb8117a09f9b8982e2c4da480a3a0e91,10000000,101,0,669129";
mempoolData = "{\"txid\":\"e99ea06aefc824fd45031447f7a0b56efb8117a09f9b8982e2c4da480a3a0e91\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":16739}},{\"vout\":2,\"prevout\":{\"value\":113293809}}],\"vout\":[{\"scriptpubkey_address\":\"1F14nF6zoUfJkqZrFgdmK5VX5QVwEpAnKW\",\"value\":16638},{\"scriptpubkey_address\":\"bc1q80y688ev7u43vqy964yf7feqddvt2mkm8977cm\",\"value\":11500000},{\"scriptpubkey_address\":\"bc1q9whgyc2du9mrgnxz0nl0shwpw8ugrcae0j0w8p\",\"value\":101784485}],\"size\":406,\"weight\":1291,\"fee\":9425,\"status\":{\"confirmed\":true,\"block_height\":669134}}";
Assert.assertFalse(createTxValidator(offerData).parseJsonValidateTakerFeeTx(mempoolData, btcFeeReceivers).getResult());
// UNDERPAID: Expected fee: 1029000 sats BTC, actual fee paid: 441000 sats BTC because they used the default rate of 0.003 should have been 0.007 per BTC
// after 1.6.0 we introduce additional leniency to allow the default rate (which is not stored in the DAO param change list)
offerData = "AKA,6779b7571f21a5a1af01d675bf032b8a3c571416d05345491018cbc2d016e888,147000000,441000,1,676543";
mempoolData = "{'txid':'6779b7571f21a5a1af01d675bf032b8a3c571416d05345491018cbc2d016e888','version':1,'locktime':0,'vin':[{'txid':'94c36c0a9c5c99844ddfe17ef05a3ebbe94b14d76ee4bed7b00c7d45e681b441','vout':0,'prevout':{'scriptpubkey_address':'bc1qt5uprdzeh9g4el0k9cttn40qzagvpca9q0q6vl','value':177920825},'sequence':4294967295}],'vout':[{'scriptpubkey_address':'19BNi5EpZhgBBWAt5ka7xWpJpX2ZWJEYyq','value':441000},{'scriptpubkey_address':'bc1qxxcl9dz6usrx4z456g6fg8n3u9327hl458d6mx','value':177008388},{'scriptpubkey_address':'bc1qdq0894p2nmg04ceyqgapln6addfl80zy7z36jd','value':467243}],'size':256,'weight':697,'fee':4194,'status':{'confirmed':true,'block_height':676543}}";
Assert.assertTrue(createTxValidator(offerData).parseJsonValidateTakerFeeTx(mempoolData, btcFeeReceivers).getResult());
}
@Test
public void testGoodOffers() throws InterruptedException {
Map<String, String> goodOffers = loadJsonTestData("offerTestData.json");
Map<String, String> mempoolData = loadJsonTestData("txInfo.json");
Assert.assertTrue(goodOffers.size() > 0);
Assert.assertTrue(mempoolData.size() > 0);
log.warn("TESTING GOOD OFFERS");
testOfferSet(goodOffers, mempoolData, true);
}
@Test
public void testBadOffers() throws InterruptedException {
Map<String, String> badOffers = loadJsonTestData("badOfferTestData.json");
Map<String, String> mempoolData = loadJsonTestData("txInfo.json");
Assert.assertTrue(badOffers.size() > 0);
Assert.assertTrue(mempoolData.size() > 0);
log.warn("TESTING BAD OFFERS");
testOfferSet(badOffers, mempoolData, false);
}
private void testOfferSet(Map<String, String> offers, Map<String, String> mempoolData, boolean expectedResult) {
Set<String> knownValuesList = new HashSet<>(offers.values());
knownValuesList.forEach(offerData -> {
TxValidator txValidator = createTxValidator(offerData);
log.warn("TESTING {}", txValidator.getTxId());
String jsonTxt = mempoolData.get(txValidator.getTxId());
if (jsonTxt == null || jsonTxt.isEmpty()) {
log.warn("{} was not found in the mempool", txValidator.getTxId());
Assert.assertFalse(expectedResult); // tx was not found in explorer
} else {
txValidator.parseJsonValidateMakerFeeTx(jsonTxt, btcFeeReceivers);
Assert.assertTrue(expectedResult == txValidator.getResult());
}
});
}
private Map<String, String> loadJsonTestData(String fileName) {
String json = "";
try {
json = IOUtils.toString(this.getClass().getResourceAsStream(fileName), "UTF-8");
} catch (IOException e) {
log.error(e.toString());
}
Map<String, String> map = new Gson().fromJson(json, Map.class);
return map;
}
// initialize the TxValidator with offerData to be validated
// and mock the used DaoStateService
private TxValidator createTxValidator(String offerData) {
try {
String[] y = offerData.split(",");
String txId = y[1];
long amount = Long.parseLong(y[2]);
boolean isCurrencyForMakerFeeBtc = Long.parseLong(y[4]) > 0;
DaoStateService mockedDaoStateService = mock(DaoStateService.class);
Answer<Coin> mockGetFeeRate = invocation -> {
return mockedLookupFeeRate(invocation.getArgument(0), invocation.getArgument(1));
};
Answer<Coin> mockGetParamValueAsCoin = invocation -> {
return mockedGetParamValueAsCoin(invocation.getArgument(0), invocation.getArgument(1));
};
Answer<List<Coin>> mockGetParamChangeList = invocation -> {
return mockedGetParamChangeList(invocation.getArgument(0));
};
when(mockedDaoStateService.getParamValueAsCoin(Mockito.any(Param.class), Mockito.anyInt())).thenAnswer(mockGetFeeRate);
when(mockedDaoStateService.getParamValueAsCoin(Mockito.any(Param.class), Mockito.anyString())).thenAnswer(mockGetParamValueAsCoin);
when(mockedDaoStateService.getParamChangeList(Mockito.any())).thenAnswer(mockGetParamChangeList);
TxValidator txValidator = new TxValidator(mockedDaoStateService, txId, Coin.valueOf(amount), isCurrencyForMakerFeeBtc);
return txValidator;
} catch (RuntimeException ignore) {
// If input format is not as expected we ignore entry
}
return null;
}
Coin mockedLookupFeeRate(Param param, int blockHeight) {
BsqFormatter bsqFormatter = new BsqFormatter();
LinkedHashMap<Long, String> feeMap = mockedGetFeeRateMap(param);
for (Map.Entry<Long, String> entry : feeMap.entrySet()) {
if (blockHeight >= entry.getKey()) {
if (param.equals(Param.DEFAULT_MAKER_FEE_BTC) || param.equals(Param.DEFAULT_TAKER_FEE_BTC))
return bsqFormatter.parseToBTC(entry.getValue());
else
return ParsingUtils.parseToCoin(entry.getValue(), bsqFormatter);
}
}
if (param.equals(Param.DEFAULT_MAKER_FEE_BTC) || param.equals(Param.DEFAULT_TAKER_FEE_BTC))
return bsqFormatter.parseToBTC(param.getDefaultValue());
else
return ParsingUtils.parseToCoin(param.getDefaultValue(), bsqFormatter);
}
private LinkedHashMap<Long, String> mockedGetFeeRateMap(Param param) {
LinkedHashMap<Long, String> feeMap = new LinkedHashMap<>();
if (param == Param.DEFAULT_MAKER_FEE_BSQ) {
feeMap.put(674707L, "8.66"); // https://github.com/bisq-network/proposals/issues/318
feeMap.put(670027L, "7.53");
feeMap.put(660667L, "10.06");
feeMap.put(655987L, "8.74");
feeMap.put(641947L, "7.6");
feeMap.put(632587L, "6.6");
feeMap.put(623227L, "5.75");
feeMap.put(599827L, "10.0");
feeMap.put(590467L, "13.0");
feeMap.put(585787L, "8.0");
feeMap.put(581107L, "1.6");
} else if (param == Param.DEFAULT_TAKER_FEE_BSQ) {
feeMap.put(674707L, "60.59"); // https://github.com/bisq-network/proposals/issues/318
feeMap.put(670027L, "52.68");
feeMap.put(660667L, "70.39");
feeMap.put(655987L, "61.21");
feeMap.put(641947L, "53.23");
feeMap.put(632587L, "46.30");
feeMap.put(623227L, "40.25");
feeMap.put(599827L, "30.00");
feeMap.put(590467L, "38.00");
feeMap.put(585787L, "24.00");
feeMap.put(581107L, "4.80");
} else if (param == Param.DEFAULT_MAKER_FEE_BTC) {
feeMap.put(623227L, "0.0010");
feeMap.put(585787L, "0.0020");
} else if (param == Param.DEFAULT_TAKER_FEE_BTC) {
feeMap.put(623227L, "0.0070");
feeMap.put(585787L, "0.0060");
}
return feeMap;
}
public Coin mockedGetParamValueAsCoin(Param param, String paramValue) {
BsqFormatter bsqFormatter = new BsqFormatter();
return bsqFormatter.parseParamValueToCoin(param, paramValue);
}
public List<Coin> mockedGetParamChangeList(Param param) {
BsqFormatter bsqFormatter = new BsqFormatter();
List<Coin> retVal = new ArrayList<Coin>();
Map<Long, String> feeMap = mockedGetFeeRateMap(param);
for (Map.Entry<Long, String> entry : feeMap.entrySet()) {
retVal.add(ParsingUtils.parseToCoin(entry.getValue(), bsqFormatter));
}
return retVal;
}
}

View file

@ -0,0 +1,47 @@
/*
* 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.provider.price;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.Ignore;
import org.junit.Test;
import static junit.framework.TestCase.assertTrue;
@Ignore
public class MarketPriceFeedServiceTest {
private static final Logger log = LoggerFactory.getLogger(MarketPriceFeedServiceTest.class);
@Test
public void testGetPrice() throws InterruptedException {
PriceFeedService priceFeedService = new PriceFeedService(null, null, null);
priceFeedService.setCurrencyCode("EUR");
priceFeedService.requestPriceFeed(tradeCurrency -> {
log.debug(tradeCurrency.toString());
assertTrue(true);
},
(errorMessage, throwable) -> {
log.debug(errorMessage);
assertTrue(false);
}
);
Thread.sleep(10000);
}
}

View file

@ -0,0 +1,48 @@
/*
* 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.trade;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload;
import bisq.core.offer.OpenOffer;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static protobuf.PersistableEnvelope.MessageCase.TRADABLE_LIST;
public class TradableListTest {
@Test
public void protoTesting() {
OfferPayload offerPayload = mock(OfferPayload.class, RETURNS_DEEP_STUBS);
TradableList<OpenOffer> openOfferTradableList = new TradableList<>();
protobuf.PersistableEnvelope message = (protobuf.PersistableEnvelope) openOfferTradableList.toProtoMessage();
assertEquals(message.getMessageCase(), TRADABLE_LIST);
// test adding an OpenOffer and convert toProto
Offer offer = new Offer(offerPayload);
OpenOffer openOffer = new OpenOffer(offer);
openOfferTradableList.add(openOffer);
message = (protobuf.PersistableEnvelope) openOfferTradableList.toProtoMessage();
assertEquals(message.getMessageCase(), TRADABLE_LIST);
assertEquals(1, message.getTradableList().getTradableList().size());
}
}

View file

@ -0,0 +1,178 @@
package bisq.core.trade.txproof.xmr;
import bisq.core.user.AutoConfirmSettings;
import java.time.Instant;
import java.util.Collections;
import java.util.Date;
import org.junit.Before;
import org.junit.Test;
import static bisq.core.trade.txproof.xmr.XmrTxProofParser.MAX_DATE_TOLERANCE;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
public class XmrTxProofParserTest {
private XmrTxProofModel xmrTxProofModel;
private String recipientAddressHex = "e957dac72bcec80d59b2fecacfa7522223b6a5df895b7e388e60297e85f3f867b42f43e8d9f086a99a997704ceb92bd9cd99d33952de90c9f5f93c82c62360ae";
private String txHash = "488e48ab0c7e69028d19f787ec57fd496ff114caba9ab265bfd41a3ea0e4687d";
private String txKey = "6c336e52ed537676968ee319af6983c80b869ca6a732b5962c02748b486f8f0f";
private XmrTxProofParser parser;
private Date tradeDate;
@Before
public void prepareMocksAndObjects() {
long amount = 100000000000L;
tradeDate = new Date(1574922644000L);
String serviceAddress = "127.0.0.1:8081";
AutoConfirmSettings autoConfirmSettings = new AutoConfirmSettings(true,
10,
1,
Collections.singletonList(serviceAddress),
"XMR");
// TODO using the mocking framework would be better...
String recipientAddress = "4ATyxmFGU7h3EWu5kYR6gy6iCNFCftbsjATfbuBBjsRHJM4KTwEyeiyVNNUmsfpK1kdRxs8QoPLsZanGqe1Mby43LeyWNMF";
xmrTxProofModel = new XmrTxProofModel(
"dummyTest",
txHash,
txKey,
recipientAddress,
amount,
tradeDate,
autoConfirmSettings);
parser = new XmrTxProofParser();
}
@Test
public void testJsonRoot() {
// checking what happens when bad input is provided
assertSame(parser.parse(xmrTxProofModel,
"invalid json data").getDetail(), XmrTxProofRequest.Detail.API_INVALID);
assertSame(parser.parse(xmrTxProofModel,
"").getDetail(), XmrTxProofRequest.Detail.API_INVALID);
assertSame(parser.parse(xmrTxProofModel,
"[]").getDetail(), XmrTxProofRequest.Detail.API_INVALID);
assertSame(parser.parse(xmrTxProofModel,
"{}").getDetail(), XmrTxProofRequest.Detail.API_INVALID);
}
@Test
public void testJsonTopLevel() {
// testing the top level fields: data and status
assertSame(parser.parse(xmrTxProofModel,
"{'data':{'title':''},'status':'fail'}")
.getDetail(), XmrTxProofRequest.Detail.TX_NOT_FOUND);
assertSame(parser.parse(xmrTxProofModel,
"{'data':{'title':''},'missingstatus':'success'}")
.getDetail(), XmrTxProofRequest.Detail.API_INVALID);
assertSame(parser.parse(xmrTxProofModel,
"{'missingdata':{'title':''},'status':'success'}")
.getDetail(), XmrTxProofRequest.Detail.API_INVALID);
}
@Test
public void testJsonAddress() {
assertSame(parser.parse(xmrTxProofModel,
"{'data':{'missingaddress':'irrelevant'},'status':'success'}")
.getDetail(), XmrTxProofRequest.Detail.API_INVALID);
assertSame(parser.parse(xmrTxProofModel,
"{'data':{'address':'e957dac7'},'status':'success'}")
.getDetail(), XmrTxProofRequest.Detail.ADDRESS_INVALID);
}
@Test
public void testJsonTxHash() {
String missing_tx_hash = "{'data':{'address':'" + recipientAddressHex + "'}, 'status':'success'}";
assertSame(parser.parse(xmrTxProofModel, missing_tx_hash).getDetail(), XmrTxProofRequest.Detail.API_INVALID);
String invalid_tx_hash = "{'data':{'address':'" + recipientAddressHex + "', 'tx_hash':'488e48'}, 'status':'success'}";
assertSame(parser.parse(xmrTxProofModel, invalid_tx_hash).getDetail(), XmrTxProofRequest.Detail.TX_HASH_INVALID);
}
@Test
public void testJsonTxKey() {
String missing_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " +
"'tx_hash':'" + txHash + "'}, 'status':'success'}";
assertSame(parser.parse(xmrTxProofModel, missing_tx_key).getDetail(), XmrTxProofRequest.Detail.API_INVALID);
String invalid_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " +
"'tx_hash':'" + txHash + "', " +
"'viewkey':'cdce04'}, 'status':'success'}";
assertSame(parser.parse(xmrTxProofModel, invalid_tx_key).getDetail(), XmrTxProofRequest.Detail.TX_KEY_INVALID);
}
@Test
public void testJsonTxTimestamp() {
String missing_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " +
"'tx_hash':'" + txHash + "'," +
"'viewkey':'" + txKey + "'}, 'status':'success'}";
assertSame(parser.parse(xmrTxProofModel, missing_tx_timestamp).getDetail(), XmrTxProofRequest.Detail.API_INVALID);
String invalid_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " +
"'tx_hash':'" + txHash + "', " +
"'viewkey':'" + txKey + "'," +
"'tx_timestamp':'12345'}, 'status':'success'}";
assertSame(parser.parse(xmrTxProofModel, invalid_tx_timestamp).getDetail(), XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING);
long tradeTimeSec = tradeDate.getTime() / 1000;
String ts = String.valueOf(tradeTimeSec - MAX_DATE_TOLERANCE - 1);
String invalid_tx_timestamp_1ms_too_old = "{'data':{'address':'" + recipientAddressHex + "', " +
"'tx_hash':'" + txHash + "', " +
"'viewkey':'" + txKey + "'," +
"'tx_timestamp':'" + ts + "'}, 'status':'success'}";
assertSame(parser.parse(xmrTxProofModel, invalid_tx_timestamp_1ms_too_old).getDetail(), XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING);
ts = String.valueOf(tradeTimeSec - MAX_DATE_TOLERANCE);
String valid_tx_timestamp_exact_MAX_DATE_TOLERANCE = "{'data':{'address':'" + recipientAddressHex + "', " +
"'tx_hash':'" + txHash + "', " +
"'viewkey':'" + txKey + "'," +
"'tx_timestamp':'" + ts + "'}, 'status':'success'}";
parser.parse(xmrTxProofModel, valid_tx_timestamp_exact_MAX_DATE_TOLERANCE);
assertNotSame(parser.parse(xmrTxProofModel, valid_tx_timestamp_exact_MAX_DATE_TOLERANCE).getDetail(), XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING);
ts = String.valueOf(tradeTimeSec - MAX_DATE_TOLERANCE + 1);
String valid_tx_timestamp_less_than_MAX_DATE_TOLERANCE = "{'data':{'address':'" + recipientAddressHex + "', " +
"'tx_hash':'" + txHash + "', " +
"'viewkey':'" + txKey + "'," +
"'tx_timestamp':'" + ts + "'}, 'status':'success'}";
assertNotSame(parser.parse(xmrTxProofModel, valid_tx_timestamp_less_than_MAX_DATE_TOLERANCE).getDetail(), XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING);
}
@Test
public void testJsonTxConfirmation() {
long epochDate = Instant.now().toEpochMilli() / 1000;
String outputs = "'outputs':[" +
"{'amount':100000000000,'match':true,'output_idx':0,'output_pubkey':'972a2c9178876f1fae4ecd22f9d7c132a12706db8ffb5d1f223f9aa8ced75b61'}," +
"{'amount':0,'match':false,'output_idx':1,'output_pubkey':'658330d2d56c74aca3b40900c56cd0f0111e2876be677ade493d06d539a1bab0'}],";
String json = "{'status':'success', 'data':{" +
"'address':'" + recipientAddressHex + "', " +
outputs +
"'tx_confirmations':777, " +
"'tx_hash':'" + txHash + "', " +
"'viewkey':'" + txKey + "', " +
"'tx_timestamp':'" + epochDate + "'}" +
"}";
assertSame(parser.parse(xmrTxProofModel, json), XmrTxProofRequest.Result.SUCCESS);
json = json.replaceFirst("777", "0");
assertSame(parser.parse(xmrTxProofModel, json).getDetail(), XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS);
json = json.replaceFirst("100000000000", "100000000001");
assertSame(parser.parse(xmrTxProofModel, json).getDetail(), XmrTxProofRequest.Detail.AMOUNT_NOT_MATCHING);
// Revert change of amount
json = json.replaceFirst("100000000001", "100000000000");
json = json.replaceFirst("'match':true", "'match':false");
assertSame(parser.parse(xmrTxProofModel, json).getDetail(), XmrTxProofRequest.Detail.NO_MATCH_FOUND);
}
@Test
public void testJsonFail() {
String failedJson = "{\"data\":null,\"message\":\"Cant parse tx hash: a\",\"status\":\"error\"}";
assertSame(parser.parse(xmrTxProofModel, failedJson).getDetail(), XmrTxProofRequest.Detail.API_INVALID);
}
}

View file

@ -0,0 +1,142 @@
/*
* 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.user;
import bisq.core.btc.nodes.LocalBitcoinNode;
import bisq.core.locale.CountryUtil;
import bisq.core.locale.CryptoCurrency;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.FiatCurrency;
import bisq.core.locale.GlobalSettings;
import bisq.core.locale.Res;
import bisq.common.config.Config;
import bisq.common.persistence.PersistenceManager;
import javafx.collections.ObservableList;
import java.util.ArrayList;
import java.util.Currency;
import java.util.List;
import java.util.Locale;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class PreferencesTest {
private Preferences preferences;
private PersistenceManager persistenceManager;
@Before
public void setUp() {
final Locale en_US = new Locale("en", "US");
Locale.setDefault(en_US);
GlobalSettings.setLocale(en_US);
Res.setBaseCurrencyCode("BTC");
Res.setBaseCurrencyName("Bitcoin");
persistenceManager = mock(PersistenceManager.class);
Config config = new Config();
LocalBitcoinNode localBitcoinNode = new LocalBitcoinNode(config);
preferences = new Preferences(
persistenceManager, config, null, localBitcoinNode, null, null, Config.DEFAULT_FULL_DAO_NODE,
null, null, Config.UNSPECIFIED_PORT);
}
@Test
public void testAddFiatCurrency() {
final FiatCurrency usd = new FiatCurrency("USD");
final FiatCurrency usd2 = new FiatCurrency("USD");
final ObservableList<FiatCurrency> fiatCurrencies = preferences.getFiatCurrenciesAsObservable();
preferences.addFiatCurrency(usd);
assertEquals(1, fiatCurrencies.size());
preferences.addFiatCurrency(usd2);
assertEquals(1, fiatCurrencies.size());
}
@Test
public void testGetUniqueListOfFiatCurrencies() {
PreferencesPayload payload = mock(PreferencesPayload.class);
List<FiatCurrency> fiatCurrencies = CurrencyUtil.getMainFiatCurrencies();
final FiatCurrency usd = new FiatCurrency("USD");
fiatCurrencies.add(usd);
when(persistenceManager.getPersisted(anyString())).thenReturn(payload);
when(payload.getUserLanguage()).thenReturn("en");
when(payload.getUserCountry()).thenReturn(CountryUtil.getDefaultCountry());
when(payload.getPreferredTradeCurrency()).thenReturn(usd);
when(payload.getFiatCurrencies()).thenReturn(fiatCurrencies);
preferences.readPersisted(() -> {
assertEquals(7, preferences.getFiatCurrenciesAsObservable().size());
assertTrue(preferences.getFiatCurrenciesAsObservable().contains(usd));
});
}
@Test
public void testGetUniqueListOfCryptoCurrencies() {
PreferencesPayload payload = mock(PreferencesPayload.class);
List<CryptoCurrency> cryptoCurrencies = CurrencyUtil.getMainCryptoCurrencies();
final CryptoCurrency dash = new CryptoCurrency("DASH", "Dash");
cryptoCurrencies.add(dash);
when(persistenceManager.getPersisted(anyString())).thenReturn(payload);
when(payload.getUserLanguage()).thenReturn("en");
when(payload.getUserCountry()).thenReturn(CountryUtil.getDefaultCountry());
when(payload.getPreferredTradeCurrency()).thenReturn(new FiatCurrency("USD"));
when(payload.getCryptoCurrencies()).thenReturn(cryptoCurrencies);
preferences.readPersisted(() -> {
assertTrue(preferences.getCryptoCurrenciesAsObservable().contains(dash));
});
}
@Test
public void testUpdateOfPersistedFiatCurrenciesAfterLocaleChanged() {
PreferencesPayload payload = mock(PreferencesPayload.class);
List<FiatCurrency> fiatCurrencies = new ArrayList<>();
final FiatCurrency usd = new FiatCurrency(Currency.getInstance("USD"), new Locale("de", "AT"));
fiatCurrencies.add(usd);
assertEquals("US-Dollar (USD)", usd.getNameAndCode());
when(persistenceManager.getPersisted(anyString())).thenReturn(payload);
when(payload.getUserLanguage()).thenReturn("en");
when(payload.getUserCountry()).thenReturn(CountryUtil.getDefaultCountry());
when(payload.getPreferredTradeCurrency()).thenReturn(usd);
when(payload.getFiatCurrencies()).thenReturn(fiatCurrencies);
preferences.readPersisted(() -> {
assertEquals("US Dollar (USD)", preferences.getFiatCurrenciesAsObservable().get(0).getNameAndCode());
});
}
}

View file

@ -0,0 +1,80 @@
/*
* 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.user;
import bisq.core.alert.Alert;
import bisq.core.arbitration.ArbitratorTest;
import bisq.core.arbitration.MediatorTest;
import bisq.core.filter.Filter;
import bisq.core.proto.CoreProtoResolver;
import com.google.common.collect.Lists;
import java.util.HashSet;
import org.junit.Ignore;
public class UserPayloadModelVOTest {
@Ignore("TODO InvalidKeySpecException at bisq.common.crypto.Sig.getPublicKeyFromBytes(Sig.java:135)")
public void testRoundtrip() {
UserPayload vo = new UserPayload();
vo.setAccountId("accountId");
UserPayload newVo = UserPayload.fromProto(vo.toProtoMessage().getUserPayload(), new CoreProtoResolver());
}
@Ignore("TODO InvalidKeySpecException at bisq.common.crypto.Sig.getPublicKeyFromBytes(Sig.java:135)")
public void testRoundtripFull() {
UserPayload vo = new UserPayload();
vo.setAccountId("accountId");
vo.setDisplayedAlert(new Alert("message", true, false, "version", new byte[]{12, -64, 12}, "string", null));
vo.setDevelopersFilter(new Filter(Lists.newArrayList(),
Lists.newArrayList(),
Lists.newArrayList(),
Lists.newArrayList(),
Lists.newArrayList(),
Lists.newArrayList(),
Lists.newArrayList(),
Lists.newArrayList(),
false,
Lists.newArrayList(),
false,
null,
null,
Lists.newArrayList(),
Lists.newArrayList(),
Lists.newArrayList(),
Lists.newArrayList(),
null,
0,
null,
null,
null,
null,
false,
Lists.newArrayList(),
new HashSet<>(),
false,
false));
vo.setRegisteredArbitrator(ArbitratorTest.getArbitratorMock());
vo.setRegisteredMediator(MediatorTest.getMediatorMock());
vo.setAcceptedArbitrators(Lists.newArrayList(ArbitratorTest.getArbitratorMock()));
vo.setAcceptedMediators(Lists.newArrayList(MediatorTest.getMediatorMock()));
UserPayload newVo = UserPayload.fromProto(vo.toProtoMessage().getUserPayload(), new CoreProtoResolver());
}
}

View file

@ -0,0 +1,147 @@
/*
* 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.util;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.param.Param;
import bisq.core.filter.Filter;
import bisq.core.filter.FilterManager;
import com.google.common.collect.Lists;
import com.google.common.primitives.Longs;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class FeeReceiverSelectorTest {
@Mock
private DaoFacade daoFacade;
@Mock
private FilterManager filterManager;
@Test
public void testGetAddress() {
Random rnd = new Random(123);
when(filterManager.getFilter()).thenReturn(filterWithReceivers(
List.of("", "foo#0.001", "ill-formed", "bar#0.002", "baz#0.001", "partial#bad")));
Map<String, Integer> selectionCounts = new HashMap<>();
for (int i = 0; i < 400; i++) {
String address = FeeReceiverSelector.getAddress(daoFacade, filterManager, rnd);
selectionCounts.compute(address, (k, n) -> n != null ? n + 1 : 1);
}
assertEquals(3, selectionCounts.size());
// Check within 2 std. of the expected values (95% confidence each):
assertEquals(100.0, selectionCounts.get("foo"), 18);
assertEquals(200.0, selectionCounts.get("bar"), 20);
assertEquals(100.0, selectionCounts.get("baz"), 18);
}
@Test
public void testGetAddress_noValidReceivers_nullFilter() {
when(daoFacade.getParamValue(Param.RECIPIENT_BTC_ADDRESS)).thenReturn("default");
when(filterManager.getFilter()).thenReturn(null);
assertEquals("default", FeeReceiverSelector.getAddress(daoFacade, filterManager));
}
@Test
public void testGetAddress_noValidReceivers_filterWithNullList() {
when(daoFacade.getParamValue(Param.RECIPIENT_BTC_ADDRESS)).thenReturn("default");
when(filterManager.getFilter()).thenReturn(filterWithReceivers(null));
assertEquals("default", FeeReceiverSelector.getAddress(daoFacade, filterManager));
}
@Test
public void testGetAddress_noValidReceivers_filterWithEmptyList() {
when(daoFacade.getParamValue(Param.RECIPIENT_BTC_ADDRESS)).thenReturn("default");
when(filterManager.getFilter()).thenReturn(filterWithReceivers(List.of()));
assertEquals("default", FeeReceiverSelector.getAddress(daoFacade, filterManager));
}
@Test
public void testGetAddress_noValidReceivers_filterWithIllFormedList() {
when(daoFacade.getParamValue(Param.RECIPIENT_BTC_ADDRESS)).thenReturn("default");
when(filterManager.getFilter()).thenReturn(filterWithReceivers(List.of("ill-formed")));
assertEquals("default", FeeReceiverSelector.getAddress(daoFacade, filterManager));
}
@Test
public void testWeightedSelection() {
Random rnd = new Random(456);
int[] selections = new int[3];
for (int i = 0; i < 6000; i++) {
selections[FeeReceiverSelector.weightedSelection(Longs.asList(1, 2, 3), rnd)]++;
}
// Check within 2 std. of the expected values (95% confidence each):
assertEquals(1000.0, selections[0], 58);
assertEquals(2000.0, selections[1], 74);
assertEquals(3000.0, selections[2], 78);
}
private static Filter filterWithReceivers(List<String> btcFeeReceiverAddresses) {
return new Filter(Lists.newArrayList(),
Lists.newArrayList(),
Lists.newArrayList(),
Lists.newArrayList(),
Lists.newArrayList(),
Lists.newArrayList(),
Lists.newArrayList(),
Lists.newArrayList(),
false,
Lists.newArrayList(),
false,
null,
null,
Lists.newArrayList(),
Lists.newArrayList(),
Lists.newArrayList(),
btcFeeReceiverAddresses,
null,
0,
null,
null,
null,
null,
false,
Lists.newArrayList(),
new HashSet<>(),
false,
false);
}
}

View file

@ -0,0 +1,69 @@
package bisq.core.util;
import bisq.core.locale.Res;
import bisq.core.monetary.Altcoin;
import bisq.core.monetary.Price;
import org.bitcoinj.utils.Fiat;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import com.natpryce.makeiteasy.Instantiator;
import com.natpryce.makeiteasy.Maker;
import com.natpryce.makeiteasy.Property;
import org.junit.Before;
import org.junit.Test;
import static com.natpryce.makeiteasy.MakeItEasy.a;
import static com.natpryce.makeiteasy.MakeItEasy.make;
import static com.natpryce.makeiteasy.MakeItEasy.with;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class FormattingUtilsTest {
private static final Property<Price, String> currencyCode = new Property<>();
private static final Property<Price, String> priceString = new Property<>();
private static final Maker<Price> usdPrice = a(lookup ->
new Price(Fiat.parseFiat(lookup.valueOf(currencyCode, "USD"), lookup.valueOf(priceString, "100"))));
@Before
public void setUp() {
Locale.setDefault(new Locale("en", "US"));
Res.setBaseCurrencyCode("BTC");
Res.setBaseCurrencyName("Bitcoin");
}
@Test
public void testFormatDurationAsWords() {
long oneDay = TimeUnit.DAYS.toMillis(1);
long oneHour = TimeUnit.HOURS.toMillis(1);
long oneMinute = TimeUnit.MINUTES.toMillis(1);
long oneSecond = TimeUnit.SECONDS.toMillis(1);
assertEquals("1 hour, 0 minutes", FormattingUtils.formatDurationAsWords(oneHour));
assertEquals("1 day, 0 hours, 0 minutes", FormattingUtils.formatDurationAsWords(oneDay));
assertEquals("2 days, 0 hours, 1 minute", FormattingUtils.formatDurationAsWords(oneDay * 2 + oneMinute));
assertEquals("2 days, 0 hours, 2 minutes", FormattingUtils.formatDurationAsWords(oneDay * 2 + oneMinute * 2));
assertEquals("1 hour, 0 minutes, 0 seconds", FormattingUtils.formatDurationAsWords(oneHour, true, true));
assertEquals("1 hour, 0 minutes, 1 second", FormattingUtils.formatDurationAsWords(oneHour + oneSecond, true, true));
assertEquals("1 hour, 0 minutes, 2 seconds", FormattingUtils.formatDurationAsWords(oneHour + oneSecond * 2, true, true));
assertEquals("2 days, 21 hours, 28 minutes", FormattingUtils.formatDurationAsWords(oneDay * 2 + oneHour * 21 + oneMinute * 28));
assertEquals("110 days", FormattingUtils.formatDurationAsWords(oneDay * 110, false, false));
assertEquals("10 days, 10 hours, 10 minutes, 10 seconds", FormattingUtils.formatDurationAsWords(oneDay * 10 + oneHour * 10 + oneMinute * 10 + oneSecond * 10, true, false));
assertEquals("1 hour, 2 seconds", FormattingUtils.formatDurationAsWords(oneHour + oneSecond * 2, true, false));
assertEquals("1 hour", FormattingUtils.formatDurationAsWords(oneHour + oneSecond * 2, false, false));
assertEquals("0 hours, 0 minutes, 1 second", FormattingUtils.formatDurationAsWords(oneSecond, true, true));
assertEquals("1 second", FormattingUtils.formatDurationAsWords(oneSecond, true, false));
assertEquals("0 hours", FormattingUtils.formatDurationAsWords(oneSecond, false, false));
assertEquals("", FormattingUtils.formatDurationAsWords(0));
assertTrue(FormattingUtils.formatDurationAsWords(0).isEmpty());
}
@Test
public void testFormatPrice() {
assertEquals("100.0000", FormattingUtils.formatPrice(make(usdPrice)));
assertEquals("7098.4700", FormattingUtils.formatPrice(make(usdPrice.but(with(priceString, "7098.4700")))));
}
}

View file

@ -0,0 +1,69 @@
/*
* 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.util;
import bisq.core.offer.OpenOffer;
import bisq.common.proto.ProtoUtil;
import protobuf.OfferPayload;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@SuppressWarnings("UnusedAssignment")
public class ProtoUtilTest {
//TODO Use NetworkProtoResolver, PersistenceProtoResolver or ProtoResolver which are all in bisq.common.
@Test
public void testEnum() {
OfferPayload.Direction direction = OfferPayload.Direction.SELL;
OfferPayload.Direction direction2 = OfferPayload.Direction.BUY;
OfferPayload.Direction realDirection = getDirection(direction);
OfferPayload.Direction realDirection2 = getDirection(direction2);
assertEquals("SELL", realDirection.name());
assertEquals("BUY", realDirection2.name());
}
@Test
public void testUnknownEnum() {
protobuf.OpenOffer.State result = protobuf.OpenOffer.State.PB_ERROR;
try {
OpenOffer.State finalResult = OpenOffer.State.valueOf(result.name());
fail();
} catch (IllegalArgumentException ignore) {
}
}
@Test
public void testUnknownEnumFix() {
protobuf.OpenOffer.State result = protobuf.OpenOffer.State.PB_ERROR;
try {
OpenOffer.State finalResult = ProtoUtil.enumFromProto(OpenOffer.State.class, result.name());
assertEquals(OpenOffer.State.AVAILABLE, ProtoUtil.enumFromProto(OpenOffer.State.class, "AVAILABLE"));
} catch (IllegalArgumentException e) {
fail();
}
}
public static OfferPayload.Direction getDirection(OfferPayload.Direction direction) {
return OfferPayload.Direction.valueOf(direction.name());
}
}

View file

@ -0,0 +1,313 @@
/*
* 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.util;
import bisq.core.locale.GlobalSettings;
import bisq.core.locale.Res;
import bisq.core.util.validation.RegexValidator;
import bisq.core.util.validation.RegexValidatorFactory;
import java.util.Locale;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class RegexValidatorTest {
@Before
public void setup() {
Locale.setDefault(new Locale("en", "US"));
GlobalSettings.setLocale(new Locale("en", "US"));
Res.setBaseCurrencyCode("BTC");
Res.setBaseCurrencyName("Bitcoin");
}
@Test
public void testAddressRegexValidator() {
RegexValidator regexValidator = RegexValidatorFactory.addressRegexValidator();
assertTrue(regexValidator.validate("").isValid);
assertFalse(regexValidator.validate(" ").isValid);
// onion V2 addresses
assertTrue(regexValidator.validate("abcdefghij234567.onion").isValid);
assertTrue(regexValidator.validate("abcdefghijklmnop.onion,abcdefghijklmnop.onion").isValid);
assertTrue(regexValidator.validate("abcdefghijklmnop.onion, abcdefghijklmnop.onion").isValid);
assertTrue(regexValidator.validate("qrstuvwxyzABCDEF.onion,qrstuvwxyzABCDEF.onion,aaaaaaaaaaaaaaaa.onion").isValid);
assertTrue(regexValidator.validate("GHIJKLMNOPQRSTUV.onion:9999").isValid);
assertTrue(regexValidator.validate("WXYZ234567abcdef.onion,GHIJKLMNOPQRSTUV.onion:9999").isValid);
assertTrue(regexValidator.validate("aaaaaaaaaaaaaaaa.onion:9999,WXYZ234567abcdef.onion:9999,2222222222222222.onion:9999").isValid);
assertFalse(regexValidator.validate("abcd.onion").isValid);
assertFalse(regexValidator.validate("abcdefghijklmnop,abcdefghijklmnop.onion").isValid);
assertFalse(regexValidator.validate("abcdefghi2345689.onion:9999").isValid);
assertFalse(regexValidator.validate("onion:9999,abcdefghijklmnop.onion:9999").isValid);
assertFalse(regexValidator.validate("abcdefghijklmnop.onion:").isValid);
// onion v3 addresses
assertFalse(regexValidator.validate("32zzibxmqi2ybxpqyggwwuwz7a3lbvtzoloti7cxoevyvijexvgsfei.onion:8333").isValid); // 1 missing char
assertTrue(regexValidator.validate("wizseedscybbttk4bmb2lzvbuk2jtect37lcpva4l3twktmkzemwbead.onion:8000").isValid);
// ipv4 addresses
assertTrue(regexValidator.validate("12.34.56.78").isValid);
assertTrue(regexValidator.validate("12.34.56.78,87.65.43.21").isValid);
assertTrue(regexValidator.validate("12.34.56.78:8888").isValid);
assertFalse(regexValidator.validate("12.34.56.788").isValid);
assertFalse(regexValidator.validate("12.34.56.78:").isValid);
// ipv6 addresses
assertTrue(regexValidator.validate("FE80:0000:0000:0000:0202:B3FF:FE1E:8329").isValid);
assertTrue(regexValidator.validate("FE80::0202:B3FF:FE1E:8329").isValid);
assertTrue(regexValidator.validate("FE80::0202:B3FF:FE1E:8329,FE80:0000:0000:0000:0202:B3FF:FE1E:8329").isValid);
assertTrue(regexValidator.validate("::1").isValid);
assertTrue(regexValidator.validate("fe80::").isValid);
assertTrue(regexValidator.validate("2001::").isValid);
assertTrue(regexValidator.validate("[::1]:8333").isValid);
assertTrue(regexValidator.validate("[FE80::0202:B3FF:FE1E:8329]:8333").isValid);
assertTrue(regexValidator.validate("[2001:db8::1]:80").isValid);
assertTrue(regexValidator.validate("[aaaa::bbbb]:8333").isValid);
assertFalse(regexValidator.validate("1200:0000:AB00:1234:O000:2552:7777:1313").isValid);
// fqdn addresses
assertTrue(regexValidator.validate("example.com").isValid);
assertTrue(regexValidator.validate("mynode.local:8333").isValid);
assertTrue(regexValidator.validate("foo.example.com,bar.example.com").isValid);
assertTrue(regexValidator.validate("foo.example.com:8333,bar.example.com:8333").isValid);
assertFalse(regexValidator.validate("mynode.local:65536").isValid);
assertFalse(regexValidator.validate("-example.com").isValid);
assertFalse(regexValidator.validate("example-.com").isValid);
}
@Test
public void testOnionAddressRegexValidator() {
RegexValidator regexValidator = RegexValidatorFactory.onionAddressRegexValidator();
assertTrue(regexValidator.validate("").isValid);
assertFalse(regexValidator.validate(" ").isValid);
// onion V2 addresses
assertTrue(regexValidator.validate("abcdefghij234567.onion").isValid);
assertTrue(regexValidator.validate("abcdefghijklmnop.onion,abcdefghijklmnop.onion").isValid);
assertTrue(regexValidator.validate("abcdefghijklmnop.onion, abcdefghijklmnop.onion").isValid);
assertTrue(regexValidator.validate("qrstuvwxyzABCDEF.onion,qrstuvwxyzABCDEF.onion,aaaaaaaaaaaaaaaa.onion").isValid);
assertTrue(regexValidator.validate("GHIJKLMNOPQRSTUV.onion:9999").isValid);
assertTrue(regexValidator.validate("WXYZ234567abcdef.onion,GHIJKLMNOPQRSTUV.onion:9999").isValid);
assertTrue(regexValidator.validate("aaaaaaaaaaaaaaaa.onion:9999,WXYZ234567abcdef.onion:9999,2222222222222222.onion:9999").isValid);
assertFalse(regexValidator.validate("abcd.onion").isValid);
assertFalse(regexValidator.validate("abcdefghijklmnop,abcdefghijklmnop.onion").isValid);
assertFalse(regexValidator.validate("abcdefghi2345689.onion:9999").isValid);
assertFalse(regexValidator.validate("onion:9999,abcdefghijklmnop.onion:9999").isValid);
assertFalse(regexValidator.validate("abcdefghijklmnop.onion:").isValid);
// onion v3 addresses
assertFalse(regexValidator.validate("32zzibxmqi2ybxpqyggwwuwz7a3lbvtzoloti7cxoevyvijexvgsfei.onion:8333").isValid); // 1 missing char
assertTrue(regexValidator.validate("wizseedscybbttk4bmb2lzvbuk2jtect37lcpva4l3twktmkzemwbead.onion:8000").isValid);
}
@Test
public void testLocalnetAddressRegexValidator() {
RegexValidator regexValidator = RegexValidatorFactory.localnetAddressRegexValidator();
assertTrue(regexValidator.validate("").isValid);
assertFalse(regexValidator.validate(" ").isValid);
// onion V2 addresses
assertFalse(regexValidator.validate("abcdefghij234567.onion").isValid);
assertFalse(regexValidator.validate("abcdefghijklmnop.onion,abcdefghijklmnop.onion").isValid);
assertFalse(regexValidator.validate("abcdefghijklmnop.onion, abcdefghijklmnop.onion").isValid);
assertFalse(regexValidator.validate("qrstuvwxyzABCDEF.onion,qrstuvwxyzABCDEF.onion,aaaaaaaaaaaaaaaa.onion").isValid);
assertFalse(regexValidator.validate("GHIJKLMNOPQRSTUV.onion:9999").isValid);
assertFalse(regexValidator.validate("WXYZ234567abcdef.onion,GHIJKLMNOPQRSTUV.onion:9999").isValid);
assertFalse(regexValidator.validate("aaaaaaaaaaaaaaaa.onion:9999,WXYZ234567abcdef.onion:9999,2222222222222222.onion:9999").isValid);
assertFalse(regexValidator.validate("abcd.onion").isValid);
assertFalse(regexValidator.validate("abcdefghijklmnop,abcdefghijklmnop.onion").isValid);
assertFalse(regexValidator.validate("abcdefghi2345689.onion:9999").isValid);
assertFalse(regexValidator.validate("onion:9999,abcdefghijklmnop.onion:9999").isValid);
assertFalse(regexValidator.validate("abcdefghijklmnop.onion:").isValid);
// onion v3 addresses
assertFalse(regexValidator.validate("32zzibxmqi2ybxpqyggwwuwz7a3lbvtzoloti7cxoevyvijexvgsfei.onion:8333").isValid); // 1 missing char
assertFalse(regexValidator.validate("wizseedscybbttk4bmb2lzvbuk2jtect37lcpva4l3twktmkzemwbead.onion:8000").isValid);
// ipv4 addresses
assertFalse(regexValidator.validate("12.34.56.78").isValid);
assertFalse(regexValidator.validate("12.34.56.78,87.65.43.21").isValid);
assertFalse(regexValidator.validate("12.34.56.78:8888").isValid);
assertFalse(regexValidator.validate("12.34.56.788").isValid);
assertFalse(regexValidator.validate("12.34.56.78:").isValid);
// ipv4 local addresses
assertTrue(regexValidator.validate("10.10.10.10").isValid);
assertTrue(regexValidator.validate("172.19.1.1").isValid);
assertTrue(regexValidator.validate("172.19.1.1").isValid);
assertTrue(regexValidator.validate("192.168.1.1").isValid);
assertTrue(regexValidator.validate("192.168.1.1,172.16.1.1").isValid);
assertTrue(regexValidator.validate("192.168.1.1:8888,192.168.1.2:8888").isValid);
assertFalse(regexValidator.validate("192.168.1.888").isValid);
assertFalse(regexValidator.validate("192.168.1.1:").isValid);
// ipv4 autolocal addresses
assertTrue(regexValidator.validate("169.254.123.232").isValid);
// ipv6 local addresses
assertTrue(regexValidator.validate("fe80:2:3:4:5:6:7:8").isValid);
assertTrue(regexValidator.validate("fe80::").isValid);
assertTrue(regexValidator.validate("fc00::").isValid);
assertTrue(regexValidator.validate("fd00::,fe80::1").isValid);
assertTrue(regexValidator.validate("fd00::8").isValid);
assertTrue(regexValidator.validate("fd00::7:8").isValid);
assertTrue(regexValidator.validate("fd00::6:7:8").isValid);
assertTrue(regexValidator.validate("fd00::5:6:7:8").isValid);
assertTrue(regexValidator.validate("fd00::4:5:6:7:8").isValid);
assertTrue(regexValidator.validate("fd00::3:4:5:6:7:8").isValid);
assertTrue(regexValidator.validate("fd00:2:3:4:5:6:7:8").isValid);
assertTrue(regexValidator.validate("fd00::0202:B3FF:FE1E:8329").isValid);
assertTrue(regexValidator.validate("fd00::0202:B3FF:FE1E:8329,FE80::0202:B3FF:FE1E:8329").isValid);
// ipv6 local with optional port at the end
assertTrue(regexValidator.validate("[fd00::1]:8081").isValid);
assertTrue(regexValidator.validate("[fd00::1]:8081,[fc00::1]:8081").isValid);
assertTrue(regexValidator.validate("[FE80::0202:B3FF:FE1E:8329]:8333").isValid);
// ipv6 loopback
assertFalse(regexValidator.validate("::1").isValid);
// ipv6 unicast
assertFalse(regexValidator.validate("2001::").isValid);
assertFalse(regexValidator.validate("[::1]:8333").isValid);
assertFalse(regexValidator.validate("[2001:db8::1]:80").isValid);
assertFalse(regexValidator.validate("[aaaa::bbbb]:8333").isValid);
assertFalse(regexValidator.validate("1200:0000:AB00:1234:O000:2552:7777:1313").isValid);
// *.local fqdn hostnames
assertTrue(regexValidator.validate("mynode.local").isValid);
assertTrue(regexValidator.validate("mynode.local:8081").isValid);
// non-local fqdn hostnames
assertFalse(regexValidator.validate("example.com").isValid);
assertFalse(regexValidator.validate("foo.example.com,bar.example.com").isValid);
assertFalse(regexValidator.validate("foo.example.com:8333,bar.example.com:8333").isValid);
// invalid fqdn hostnames
assertFalse(regexValidator.validate("mynode.local:65536").isValid);
assertFalse(regexValidator.validate("-example.com").isValid);
assertFalse(regexValidator.validate("example-.com").isValid);
}
@Test
public void testLocalhostAddressRegexValidator() {
RegexValidator regexValidator = RegexValidatorFactory.localhostAddressRegexValidator();
assertTrue(regexValidator.validate("").isValid);
assertFalse(regexValidator.validate(" ").isValid);
// onion V2 addresses
assertFalse(regexValidator.validate("abcdefghij234567.onion").isValid);
assertFalse(regexValidator.validate("abcdefghijklmnop.onion,abcdefghijklmnop.onion").isValid);
assertFalse(regexValidator.validate("abcdefghijklmnop.onion, abcdefghijklmnop.onion").isValid);
assertFalse(regexValidator.validate("qrstuvwxyzABCDEF.onion,qrstuvwxyzABCDEF.onion,aaaaaaaaaaaaaaaa.onion").isValid);
assertFalse(regexValidator.validate("GHIJKLMNOPQRSTUV.onion:9999").isValid);
assertFalse(regexValidator.validate("WXYZ234567abcdef.onion,GHIJKLMNOPQRSTUV.onion:9999").isValid);
assertFalse(regexValidator.validate("aaaaaaaaaaaaaaaa.onion:9999,WXYZ234567abcdef.onion:9999,2222222222222222.onion:9999").isValid);
assertFalse(regexValidator.validate("abcd.onion").isValid);
assertFalse(regexValidator.validate("abcdefghijklmnop,abcdefghijklmnop.onion").isValid);
assertFalse(regexValidator.validate("abcdefghi2345689.onion:9999").isValid);
assertFalse(regexValidator.validate("onion:9999,abcdefghijklmnop.onion:9999").isValid);
assertFalse(regexValidator.validate("abcdefghijklmnop.onion:").isValid);
// onion v3 addresses
assertFalse(regexValidator.validate("32zzibxmqi2ybxpqyggwwuwz7a3lbvtzoloti7cxoevyvijexvgsfei.onion:8333").isValid); // 1 missing char
assertFalse(regexValidator.validate("wizseedscybbttk4bmb2lzvbuk2jtect37lcpva4l3twktmkzemwbead.onion:8000").isValid);
// ipv4 addresses
assertFalse(regexValidator.validate("12.34.56.78").isValid);
assertFalse(regexValidator.validate("12.34.56.78,87.65.43.21").isValid);
assertFalse(regexValidator.validate("12.34.56.78:8888").isValid);
assertFalse(regexValidator.validate("12.34.56.788").isValid);
assertFalse(regexValidator.validate("12.34.56.78:").isValid);
// ipv4 loopback addresses
assertTrue(regexValidator.validate("127.0.0.1").isValid);
assertTrue(regexValidator.validate("127.0.1.1").isValid);
// ipv4 local addresses
assertFalse(regexValidator.validate("10.10.10.10").isValid);
assertFalse(regexValidator.validate("172.19.1.1").isValid);
assertFalse(regexValidator.validate("172.19.1.1").isValid);
assertFalse(regexValidator.validate("192.168.1.1").isValid);
assertFalse(regexValidator.validate("192.168.1.1,172.16.1.1").isValid);
assertFalse(regexValidator.validate("192.168.1.1:8888,192.168.1.2:8888").isValid);
assertFalse(regexValidator.validate("192.168.1.888").isValid);
assertFalse(regexValidator.validate("192.168.1.1:").isValid);
// ipv4 autolocal addresses
assertFalse(regexValidator.validate("169.254.123.232").isValid);
// ipv6 local addresses
assertFalse(regexValidator.validate("fe80::").isValid);
assertFalse(regexValidator.validate("fc00::").isValid);
assertFalse(regexValidator.validate("fd00::8").isValid);
assertFalse(regexValidator.validate("fd00::7:8").isValid);
assertFalse(regexValidator.validate("fd00::6:7:8").isValid);
assertFalse(regexValidator.validate("fd00::5:6:7:8").isValid);
assertFalse(regexValidator.validate("fd00::3:4:5:6:7:8").isValid);
assertFalse(regexValidator.validate("fd00::4:5:6:7:8").isValid);
assertFalse(regexValidator.validate("fd00:2:3:4:5:6:7:8").isValid);
assertFalse(regexValidator.validate("fd00::0202:B3FF:FE1E:8329").isValid);
assertFalse(regexValidator.validate("FE80:0000:0000:0000:0202:B3FF:FE1E:8329").isValid);
assertFalse(regexValidator.validate("FE80::0202:B3FF:FE1E:8329").isValid);
assertFalse(regexValidator.validate("FE80::0202:B3FF:FE1E:8329,FE80:0000:0000:0000:0202:B3FF:FE1E:8329").isValid);
// ipv6 local with optional port at the end
assertFalse(regexValidator.validate("[fd00::1]:8081").isValid);
assertFalse(regexValidator.validate("[fd00::1]:8081,[fc00::1]:8081").isValid);
// ipv6 loopback
assertTrue(regexValidator.validate("::1").isValid);
assertTrue(regexValidator.validate("::2").isValid);
assertTrue(regexValidator.validate("[::1]:8333").isValid);
// ipv6 unicast
assertFalse(regexValidator.validate("2001::").isValid);
assertFalse(regexValidator.validate("[FE80::0202:B3FF:FE1E:8329]:8333").isValid);
assertFalse(regexValidator.validate("[2001:db8::1]:80").isValid);
assertFalse(regexValidator.validate("[aaaa::bbbb]:8333").isValid);
assertFalse(regexValidator.validate("1200:0000:AB00:1234:O000:2552:7777:1313").isValid);
// localhost fqdn hostnames
assertTrue(regexValidator.validate("localhost").isValid);
assertTrue(regexValidator.validate("localhost:8081").isValid);
// local fqdn hostnames
assertFalse(regexValidator.validate("mynode.local:8081").isValid);
// non-local fqdn hostnames
assertFalse(regexValidator.validate("example.com").isValid);
assertFalse(regexValidator.validate("foo.example.com,bar.example.com").isValid);
assertFalse(regexValidator.validate("foo.example.com:8333,bar.example.com:8333").isValid);
// invalid fqdn hostnames
assertFalse(regexValidator.validate("mynode.local:65536").isValid);
assertFalse(regexValidator.validate("-example.com").isValid);
assertFalse(regexValidator.validate("example-.com").isValid);
}
}

View file

@ -0,0 +1,123 @@
/*
* 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.util.coin;
import bisq.core.monetary.Price;
import org.bitcoinj.core.Coin;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class CoinUtilTest {
@Test
public void testGetFeePerBtc() {
assertEquals(Coin.parseCoin("1"), CoinUtil.getFeePerBtc(Coin.parseCoin("1"), Coin.parseCoin("1")));
assertEquals(Coin.parseCoin("0.1"), CoinUtil.getFeePerBtc(Coin.parseCoin("0.1"), Coin.parseCoin("1")));
assertEquals(Coin.parseCoin("0.01"), CoinUtil.getFeePerBtc(Coin.parseCoin("0.1"), Coin.parseCoin("0.1")));
assertEquals(Coin.parseCoin("0.015"), CoinUtil.getFeePerBtc(Coin.parseCoin("0.3"), Coin.parseCoin("0.05")));
}
@Test
public void testMinCoin() {
assertEquals(Coin.parseCoin("1"), CoinUtil.minCoin(Coin.parseCoin("1"), Coin.parseCoin("1")));
assertEquals(Coin.parseCoin("0.1"), CoinUtil.minCoin(Coin.parseCoin("0.1"), Coin.parseCoin("1")));
assertEquals(Coin.parseCoin("0.01"), CoinUtil.minCoin(Coin.parseCoin("0.1"), Coin.parseCoin("0.01")));
assertEquals(Coin.parseCoin("0"), CoinUtil.minCoin(Coin.parseCoin("0"), Coin.parseCoin("0.05")));
assertEquals(Coin.parseCoin("0"), CoinUtil.minCoin(Coin.parseCoin("0.05"), Coin.parseCoin("0")));
}
@Test
public void testMaxCoin() {
assertEquals(Coin.parseCoin("1"), CoinUtil.maxCoin(Coin.parseCoin("1"), Coin.parseCoin("1")));
assertEquals(Coin.parseCoin("1"), CoinUtil.maxCoin(Coin.parseCoin("0.1"), Coin.parseCoin("1")));
assertEquals(Coin.parseCoin("0.1"), CoinUtil.maxCoin(Coin.parseCoin("0.1"), Coin.parseCoin("0.01")));
assertEquals(Coin.parseCoin("0.05"), CoinUtil.maxCoin(Coin.parseCoin("0"), Coin.parseCoin("0.05")));
assertEquals(Coin.parseCoin("0.05"), CoinUtil.maxCoin(Coin.parseCoin("0.05"), Coin.parseCoin("0")));
}
@Test
public void testGetAdjustedAmount() {
Coin result = CoinUtil.getAdjustedAmount(
Coin.valueOf(100_000),
Price.valueOf("USD", 1000_0000),
20_000_000,
1);
assertEquals(
"Minimum trade amount allowed should be adjusted to the smallest trade allowed.",
"0.001 BTC",
result.toFriendlyString()
);
try {
CoinUtil.getAdjustedAmount(
Coin.ZERO,
Price.valueOf("USD", 1000_0000),
20_000_000,
1);
fail("Expected IllegalArgumentException to be thrown when amount is too low.");
} catch (IllegalArgumentException iae) {
assertEquals(
"Unexpected exception message.",
"amount needs to be above minimum of 10k satoshis",
iae.getMessage()
);
}
result = CoinUtil.getAdjustedAmount(
Coin.valueOf(1_000_000),
Price.valueOf("USD", 1000_0000),
20_000_000,
1);
assertEquals(
"Minimum allowed trade amount should not be adjusted.",
"0.01 BTC",
result.toFriendlyString()
);
result = CoinUtil.getAdjustedAmount(
Coin.valueOf(100_000),
Price.valueOf("USD", 1000_0000),
1_000_000,
1);
assertEquals(
"Minimum trade amount allowed should respect maxTradeLimit and factor, if possible.",
"0.001 BTC",
result.toFriendlyString()
);
// TODO(chirhonul): The following seems like it should raise an exception or otherwise fail.
// We are asking for the smallest allowed BTC trade when price is 1000 USD each, and the
// max trade limit is 5k sat = 0.00005 BTC. But the returned amount 0.00005 BTC, or
// 0.05 USD worth, which is below the factor of 1 USD, but does respect the maxTradeLimit.
// Basically the given constraints (maxTradeLimit vs factor) are impossible to both fulfill..
result = CoinUtil.getAdjustedAmount(
Coin.valueOf(100_000),
Price.valueOf("USD", 1000_0000),
5_000,
1);
assertEquals(
"Minimum trade amount allowed with low maxTradeLimit should still respect that limit, even if result does not respect the factor specified.",
"0.00005 BTC",
result.toFriendlyString()
);
}
}

View file

@ -0,0 +1,57 @@
Bisq Test version 0.1.0
Usage: bisq-test [options]
Options:
--name=<String> (default: Bisq)
The name of the Bisq node
--another-option=<String> (default: WAT)
This is a long description which will need to break over multiple
linessssssssssss such that no line is longer than 80 characters in the
help output.
--exactly-72-char-description=<String>
012345678911234567892123456789312345678941234567895123456789612345678971
--exactly-72-char-description-with-spaces=<String>
123456789 123456789 123456789 123456789 123456789 123456789 123456789 1
--90-char-description-without-spaces=<String>
-123456789-223456789-323456789-423456789-523456789-623456789-723456789-823456789-923456789
--90-char-description-with-space-at-char-80=<String>
-123456789-223456789-323456789-423456789-523456789-623456789-723456789-823456789
923456789
--90-char-description-with-spaces-at-chars-5-and-80=<String>
-123
56789-223456789-323456789-423456789-523456789-623456789-723456789-823456789
923456789
--90-char-description-with-space-at-char-73=<String>
-123456789-223456789-323456789-423456789-523456789-623456789-723456789-8
3456789-923456789
--1-char-description-with-only-a-space=<String>
--empty-description=<String>
--no-description=<String>
--no-arg
Some description
--optional-arg=<value>
Option description
--with-default-value=<String> (default: Wat)
Some option with a default value
--data-dir=<File> (default: /Users/cbeams/Library/Application Support/Bisq)
Application data directory
--enum-opt=<foo|bar|baz> (default: foo)
Some option that accepts an enum value as an argument

View file

@ -0,0 +1,57 @@
Bisq Test version 0.1.0
Usage: bisq-test [options]
Options:
--name=<String> (default: Bisq)
The name of the Bisq node
--another-option=<String> (default: WAT)
This is a long description which will need to break over multiple
linessssssssssss such that no line is longer than 80 characters in the
help output.
--exactly-72-char-description=<String>
012345678911234567892123456789312345678941234567895123456789612345678971
--exactly-72-char-description-with-spaces=<String>
123456789 123456789 123456789 123456789 123456789 123456789 123456789 1
--90-char-description-without-spaces=<String>
-123456789-223456789-323456789-423456789-523456789-623456789-723456789-823456789-923456789
--90-char-description-with-space-at-char-80=<String>
-123456789-223456789-323456789-423456789-523456789-623456789-723456789-823456789
923456789
--90-char-description-with-spaces-at-chars-5-and-80=<String>
-123
56789-223456789-323456789-423456789-523456789-623456789-723456789-823456789
923456789
--90-char-description-with-space-at-char-73=<String>
-123456789-223456789-323456789-423456789-523456789-623456789-723456789-8
3456789-923456789
--1-char-description-with-only-a-space=<String>
--empty-description=<String>
--no-description=<String>
--no-arg
Some description
--optional-arg=<value>
Option description
--with-default-value=<String> (default: Wat)
Some option with a default value
--data-dir=<File> (default: \Users\cbeams\Library\Application Support\Bisq)
Application data directory
--enum-opt=<foo|bar|baz> (default: foo)
Some option that accepts an enum value as an argument

View file

@ -0,0 +1,53 @@
00000020a33d8cf2a1567a148dad1a4099599bafa631135262413a4bdd1182be5673471abe69039afc7c93936e3e2860da8cab522281fe4139a453d1
1a3d5cfe75e93761bf25b25fffff7f20010000000b020000000001010000000000000000000000000000000000000000000000000000000000000000
ffffffff05028b000101ffffffff02495b062a0100000017a914f2d479a78b981e4a2b05be5f89ef7c468ac48e78870000000000000000266a24aa21
a9ed9f5a62816cebb2044be9db74a26762020defe9e8b251b73af2abefad7d4b355c0120000000000000000000000000000000000000000000000000
0000000000000000000000000100000001274c7174e814eb731712b80187660f7a6ab2949a3271ef80037685cdde2d78c3020000006b483045022100
b6c2fa10587d6fed3a0eecfd098b160f69a850beca139fe03ef65bec4cba1c5b02204a833a16c22bbd32722243ea3270e672f646ee9406e8797e1109
3951e92efbd5012103dcca91c2ec7229f1b4f4c4f664c92d3303dddef8d38736f6a7f28de16f3ce416ffffffff03881300000000000017a9144c0e48
93237f85479f489b32c8ff0faf3ee2e1c987c247090000000000160014007128282856f8e8f3c75909f9f1474b55cb1f1605f8902500000000160014
9bc809698674ec7c01d35d438e9d0de1aa87b6c800000000010000000114facc8cf47a984cedf9ba84db10ad767e18c6fb6edbac39ce8f138a1e5b43
9100000000da0047304402201f00d9a4aab1a3a239f1ad95a910092c0c55423480d609eaad4599cf7ecb7f480220668b1a9cf5624b1c4ece6da3f64b
c6021e509f588ae1006601acd8a9f83b357601483045022100982eca77a72a2bdba51b9231afd4521400bee1bb7830634eb26db2b0c621bc46022073
d7325916e2b5ceb1d2e510a5161fd9115105a8dafa94068864624bb10d190e014752210229713ad5c604c585128b3a5da6de20d78fc33bd3b595e999
1f4c0e1fee99f845210398ad45a74bf5a5c5a8ec31de6815d2e805a23e68c0f8001770e74bc4c17c5b3152aefeffffff01b06a21000000000017a914
4c0e4893237f85479f489b32c8ff0faf3ee2e1c9878a0000000100000000010133ba0d88494567a02bffc406b31bd5eb29f0b536a96326baca349b3a
96f241e90200000000ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987067e180000000000160014f6da24
d081a1b63dcacf4a5762a8ed91fd472c685b74b900000000001600144755561caa18d651bf59912545764811d0ab96f60247304402200d4f21475675
3861adf835f5d175835d3cd50a19edbf9881572ab7f831a030de0220181b94bbb7d7b0a7a6eee06881a3265cb28405f2d14055e1ff23ac6500471930
012102772467db1e5909e7e9f2b169daccf556e7e2981b145c756db649f3972d913c320000000001000000000101e06ec4548803dadb10ef6c66e4f3
1f319161dc9ec31631e967773b8e042836180200000000ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987
f4390900000000001600145230c895305a232ef2a4feb0a91e7d99e22fd515d20bd20000000000160014b6fbbd9053e47891fae7f3db7dd3966062b2
513c0247304402203eeb1713b582be1d74bf6a9f95c573dd41baeedf1efd1bc9a1ad1cccad97c4f70220799a399f53f9325f6cf9681b0138f80bd80f
3dc900a4d0ab5cc3c97d5be85f1801210255f56a7be9f88ccf5885ac2f8cd67320424d533d71083270a2891b2488ffb22b0000000001000000000101
4c701c32d3b0ce9408d8ec96d80934dbc6cb42df616e6e751dae82afdb46214e0200000000ffffffff03881300000000000017a9144c0e4893237f85
479f489b32c8ff0faf3ee2e1c987c02709000000000016001489c79bc0628d2d8b1cd91c2ed0e75db13e6f3f3a8a07a00600000000160014062d20a6
692350b7a39397c50857a7f725788da002483045022100ebb8e0ddab46b762e3a9555442cc7ee35c4353d9152e856c97251913902a5056022010d3a0
bb51d931a18174dc8ed0ffa37c5ff29f8e924b71d86850de31f3ea4c6e012102e0284cdeae6a8c971e2ea5004ebf9196ee9b3037d6f1ed039c4b5672
a69cddc60000000001000000000101c3f609b5166e32bd0c29168767621bfc56411a3ff9e84932fc2c612407566a370200000000ffffffff03881300
000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987006a1800000000001600141437b91493d1929b1b42a80e83229c347c28f937
eaaf90060000000016001491ad2cce99e8e4455de5f44559816b98213f3503024830450221008f8eee212f209ba2a179197bd4ba35a35ad7a3045990
25ddbd192a6e7e64c1920220242c297726948ad408ce54c9a0e0287b283c53dc68323537f24a7e3ecd8c526b012103f870bcd3a46e80e4b1236302e6
2318b412cc97ef096fc976a89deb569dc11ef1000000000100000001ba4f0ae59d7cb81f0c7edd63796387fde6825a3536953c2824d7d945c192bd20
020000006b483045022100ba97f6336b3bb3e07cf584010c7b8ab52957e34e462e71252c63f498d51f45b70220708dd78d2d9943f8c176055963ab70
6870274068fe7b1e5d87592a837336a5340121039978f14b2463d7d4790cdf2a37c2a3d872dd517ca91db7f6f7a858a7ac661c60ffffffff03881300
000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987c02709000000000016001413afc3a26c010dddaf2410a9d97b5054a4e8d309
04e05938000000001600145228ee46a95383b314396dda75e707b8bed830340000000001000000015d71dc04e56bc08b61180a2b9531a0747f56615f
f27ec51cbc72ac7a800cc27a020000006a473044022073a8a8ee9cc490093e6de5708b3727cef35f41038713fab9a5c235b4b400b73102206f97f4fc
8faefb534e85c0f3773f2d67781c9871f681211276619054fc54015201210374e07f24beca2270cf305100652149a64c80d76611f775ec276658aeae
4ef0b5ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987006a1800000000001600141c397ba7ea8410dbd5
56c51f8184a14ab015114324b6a10600000000160014c38c3d890c415f2c13f6925c1ad1d4a7cb4f7dfe0000000001000000000102142a200a7460ad
f754232337f48bf7c38ba411eed0cd2f98900ffd9f2adc1ed20100000000ffffffff99d67c2a69b04831bd229ea6022c91c68999c38d0ba92211b56f
15c5f8222bf50100000000ffffffff03e803000000000000220020223d978073802f79e6ecdc7591e5dc1f0ea7030d6466f73c6b90391bc72e886f00
00000000000000226a20758e9207848c631c6839b1382bb22a52b6ef0645d733389d7be2efb1e8b71454db972100000000001600145e41d2fb8de1c2
50410416d8dd153c685d3f9c7b024730440220073a37eb4371dc3d0cf218d6e9b8e6044275acd07402ccebdf24f65b60a3c1f70220647e71c173f992
fc0c5ec6c2b0b1653a95118098269e318c41d1bc33da3ff14f012103c3e858472f39d31c6defdf38b4778660501f0ccfa524b3dd8ba61117b7646635
0247304402202b8ef5de1c56328d3797265272540a054fc04c158b23ee6385b69b14486422c10220749f591fcf4ef995df8f1e7d9aa3cf0c045f1616
386b86b419197b360c871fca012102c73e60f00bc72b56568a9f371b9122b3ee29d41730670e98ff8da58e7bbfab280000000001000000000102913a
5817e0b3bddb0bc6869e12200b5115be11522cf76125229d47e184da48460100000000ffffffff684d66517d21106d1bcbba96964e31a532fec33c09
8cd621e2500c702340b6780100000000ffffffff03e803000000000000220020e3e81046fd9659b5725736efa404bc1c8f9b2ff6f0af7cb7ddacdcc6
1e1c72310000000000000000226a20c63aacf2e8be20752b6f689c0308967cbc335641f2948a4a7962fdde6c464730f2962100000000001600145e41
d2fb8de1c250410416d8dd153c685d3f9c7b02483045022100c9665b9abe7fcab10f775eeafc2391c1fee84c50b50df6d697b1db9a7eea5dd3022070
cb7aa57b8f5bf9f2eff11263cf2ea871ab7b9ddfc8e47671cee50ada547243012102016f9a6cc454bd1e74c28df36a079231a215812c60581d1e1745
e650f82bd1230248304502210094cdec8e08f32919b3f25c8672041305c848b4206256ad64f7090dc97dfd1bf002205c397a310cebc690fac04d1394
5e012d0031246d4574e92f97e3701ca729b6140121029b739486d7cf402b3db3913187fad7897e8a5ec3cd8607e9b5fc54a71958b03100000000

View file

@ -0,0 +1,33 @@
{
"hash": "015f37a20d517645a11a6cdd316049f41bc77b4a4057b2dd092114b78147f42c",
"confirmations": 12,
"strippedsize": 2270,
"size": 3178,
"weight": 9988,
"height": 139,
"version": 536870912,
"versionHex": "20000000",
"merkleroot": "6137e975fe5c3d1ad153a43941fe812252ab8cda60283e6e93937cfc9a0369be",
"tx": [
"09bbfd286d0399b57ac7c85956fccbe7e080cedd7a01723d8d68038f0cf57159",
"e2fc769668f7306ca09865c10dd744ed5510f1cec35b8f854c9ed346229a303b",
"b36a2c90dab09a0b99d30a3b132af37b79d8266a1510decc34683a2784228337",
"f52b22f8c5156fb51122a90b8dc39989c6912c02a69e22bd3148b0692a7cd699",
"4648da84e1479d222561f72c5211be15510b20129e86c60bdbbdb3e017583a91",
"d21edc2a9ffd0f90982fcdd0ee11a48bc3f78bf437232354f7ad60740a202a14",
"78b64023700c50e221d68c093cc3fe32a5314e9696bacb1b6d10217d51664d68",
"dd4243c2743a2d2351814a14628e1976e6fb208e63bd2bbd441180c441205027",
"aa33deb87512c2c417a0a9a17c58a36dcf2686fca8b50cc608ad952178a350c2",
"719606705c6832f7180ab9db5e1ce6a51bad80a5cbf3b57b806dd10f3d7d5124",
"16cd3283068d2965f26045e15a285e39a762af32ec388ae8c28fb6cb2c468768"
],
"time": 1605510591,
"mediantime": 1589548514,
"nonce": 1,
"bits": "207fffff",
"difficulty": 4.656542373906925E-10,
"chainwork": "0000000000000000000000000000000000000000000000000000000000000118",
"nTx": 11,
"previousblockhash": "1a477356be8211dd4b3a4162521331a6af9b5999401aad8d147a56a1f28c3da3",
"nextblockhash": "7d8267341a57f1f626b450eb22b2dbf208f13ec176e1cc015aa4d2b2ea55016d"
}

View file

@ -0,0 +1,698 @@
{
"hash": "015f37a20d517645a11a6cdd316049f41bc77b4a4057b2dd092114b78147f42c",
"confirmations": 12,
"strippedsize": 2270,
"size": 3178,
"weight": 9988,
"height": 139,
"version": 536870912,
"versionHex": "20000000",
"merkleroot": "6137e975fe5c3d1ad153a43941fe812252ab8cda60283e6e93937cfc9a0369be",
"tx": [
{
"txid": "09bbfd286d0399b57ac7c85956fccbe7e080cedd7a01723d8d68038f0cf57159",
"hash": "ed1620cdd028c99d57ee0709f963976bb75cac93f5171f5a2f5dd9976e5c56bc",
"version": 2,
"size": 171,
"vsize": 144,
"weight": 576,
"locktime": 0,
"vin": [
{
"coinbase": "028b000101",
"sequence": 4294967295
}
],
"vout": [
{
"value": 50.00026953,
"n": 0,
"scriptPubKey": {
"asm": "OP_HASH160 f2d479a78b981e4a2b05be5f89ef7c468ac48e78 OP_EQUAL",
"hex": "a914f2d479a78b981e4a2b05be5f89ef7c468ac48e7887",
"reqSigs": 1,
"type": "scripthash",
"addresses": [
"2NFPC3k5RjZ4GAQgLqJdiVdJ6gqWqEBryXq"
]
}
},
{
"value": 0.0,
"n": 1,
"scriptPubKey": {
"asm": "OP_RETURN aa21a9ed9f5a62816cebb2044be9db74a26762020defe9e8b251b73af2abefad7d4b355c",
"hex": "6a24aa21a9ed9f5a62816cebb2044be9db74a26762020defe9e8b251b73af2abefad7d4b355c",
"type": "nulldata"
}
}
],
"hex": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff05028b000101ffffffff02495b062a0100000017a914f2d479a78b981e4a2b05be5f89ef7c468ac48e78870000000000000000266a24aa21a9ed9f5a62816cebb2044be9db74a26762020defe9e8b251b73af2abefad7d4b355c0120000000000000000000000000000000000000000000000000000000000000000000000000"
},
{
"txid": "e2fc769668f7306ca09865c10dd744ed5510f1cec35b8f854c9ed346229a303b",
"hash": "e2fc769668f7306ca09865c10dd744ed5510f1cec35b8f854c9ed346229a303b",
"version": 1,
"size": 252,
"vsize": 252,
"weight": 1008,
"locktime": 0,
"vin": [
{
"txid": "c3782ddecd85760380ef71329a94b26a7a0f668701b8121773eb14e874714c27",
"vout": 2,
"scriptSig": {
"asm": "3045022100b6c2fa10587d6fed3a0eecfd098b160f69a850beca139fe03ef65bec4cba1c5b02204a833a16c22bbd32722243ea3270e672f646ee9406e8797e11093951e92efbd5[ALL] 03dcca91c2ec7229f1b4f4c4f664c92d3303dddef8d38736f6a7f28de16f3ce416",
"hex": "483045022100b6c2fa10587d6fed3a0eecfd098b160f69a850beca139fe03ef65bec4cba1c5b02204a833a16c22bbd32722243ea3270e672f646ee9406e8797e11093951e92efbd5012103dcca91c2ec7229f1b4f4c4f664c92d3303dddef8d38736f6a7f28de16f3ce416"
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 5.0E-5,
"n": 0,
"scriptPubKey": {
"asm": "OP_HASH160 4c0e4893237f85479f489b32c8ff0faf3ee2e1c9 OP_EQUAL",
"hex": "a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987",
"reqSigs": 1,
"type": "scripthash",
"addresses": [
"2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w"
]
}
},
{
"value": 0.00608194,
"n": 1,
"scriptPubKey": {
"asm": "0 007128282856f8e8f3c75909f9f1474b55cb1f16",
"hex": "0014007128282856f8e8f3c75909f9f1474b55cb1f16",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"bcrt1qqpcjs2pg2muw3u78tyylnu28fd2uk8ckr30ezc"
]
}
},
{
"value": 6.30257669,
"n": 2,
"scriptPubKey": {
"asm": "0 9bc809698674ec7c01d35d438e9d0de1aa87b6c8",
"hex": "00149bc809698674ec7c01d35d438e9d0de1aa87b6c8",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"bcrt1qn0yqj6vxwnk8cqwnt4pca8gdux4g0dkghc9rur"
]
}
}
],
"hex": "0100000001274c7174e814eb731712b80187660f7a6ab2949a3271ef80037685cdde2d78c3020000006b483045022100b6c2fa10587d6fed3a0eecfd098b160f69a850beca139fe03ef65bec4cba1c5b02204a833a16c22bbd32722243ea3270e672f646ee9406e8797e11093951e92efbd5012103dcca91c2ec7229f1b4f4c4f664c92d3303dddef8d38736f6a7f28de16f3ce416ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987c247090000000000160014007128282856f8e8f3c75909f9f1474b55cb1f1605f89025000000001600149bc809698674ec7c01d35d438e9d0de1aa87b6c800000000"
},
{
"txid": "b36a2c90dab09a0b99d30a3b132af37b79d8266a1510decc34683a2784228337",
"hash": "b36a2c90dab09a0b99d30a3b132af37b79d8266a1510decc34683a2784228337",
"version": 1,
"size": 301,
"vsize": 301,
"weight": 1204,
"locktime": 138,
"vin": [
{
"txid": "91435b1e8a138fce39acdb6efbc6187e76ad10db84baf9ed4c987af48cccfa14",
"vout": 0,
"scriptSig": {
"asm": "0 304402201f00d9a4aab1a3a239f1ad95a910092c0c55423480d609eaad4599cf7ecb7f480220668b1a9cf5624b1c4ece6da3f64bc6021e509f588ae1006601acd8a9f83b3576[ALL] 3045022100982eca77a72a2bdba51b9231afd4521400bee1bb7830634eb26db2b0c621bc46022073d7325916e2b5ceb1d2e510a5161fd9115105a8dafa94068864624bb10d190e[ALL] 52210229713ad5c604c585128b3a5da6de20d78fc33bd3b595e9991f4c0e1fee99f845210398ad45a74bf5a5c5a8ec31de6815d2e805a23e68c0f8001770e74bc4c17c5b3152ae",
"hex": "0047304402201f00d9a4aab1a3a239f1ad95a910092c0c55423480d609eaad4599cf7ecb7f480220668b1a9cf5624b1c4ece6da3f64bc6021e509f588ae1006601acd8a9f83b357601483045022100982eca77a72a2bdba51b9231afd4521400bee1bb7830634eb26db2b0c621bc46022073d7325916e2b5ceb1d2e510a5161fd9115105a8dafa94068864624bb10d190e014752210229713ad5c604c585128b3a5da6de20d78fc33bd3b595e9991f4c0e1fee99f845210398ad45a74bf5a5c5a8ec31de6815d2e805a23e68c0f8001770e74bc4c17c5b3152ae"
},
"sequence": 4294967294
}
],
"vout": [
{
"value": 0.0219,
"n": 0,
"scriptPubKey": {
"asm": "OP_HASH160 4c0e4893237f85479f489b32c8ff0faf3ee2e1c9 OP_EQUAL",
"hex": "a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987",
"reqSigs": 1,
"type": "scripthash",
"addresses": [
"2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w"
]
}
}
],
"hex": "010000000114facc8cf47a984cedf9ba84db10ad767e18c6fb6edbac39ce8f138a1e5b439100000000da0047304402201f00d9a4aab1a3a239f1ad95a910092c0c55423480d609eaad4599cf7ecb7f480220668b1a9cf5624b1c4ece6da3f64bc6021e509f588ae1006601acd8a9f83b357601483045022100982eca77a72a2bdba51b9231afd4521400bee1bb7830634eb26db2b0c621bc46022073d7325916e2b5ceb1d2e510a5161fd9115105a8dafa94068864624bb10d190e014752210229713ad5c604c585128b3a5da6de20d78fc33bd3b595e9991f4c0e1fee99f845210398ad45a74bf5a5c5a8ec31de6815d2e805a23e68c0f8001770e74bc4c17c5b3152aefeffffff01b06a21000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c9878a000000"
},
{
"txid": "f52b22f8c5156fb51122a90b8dc39989c6912c02a69e22bd3148b0692a7cd699",
"hash": "a1cb920222f6470dae2b90af42ec4da2e541bc4de9cec11441ca2dd0032bca2c",
"version": 1,
"size": 254,
"vsize": 173,
"weight": 689,
"locktime": 0,
"vin": [
{
"txid": "e941f2963a9b34caba2663a936b5f029ebd51bb306c4ff2ba0674549880dba33",
"vout": 2,
"scriptSig": {
"asm": "",
"hex": ""
},
"txinwitness": [
"304402200d4f214756753861adf835f5d175835d3cd50a19edbf9881572ab7f831a030de0220181b94bbb7d7b0a7a6eee06881a3265cb28405f2d14055e1ff23ac650047193001",
"02772467db1e5909e7e9f2b169daccf556e7e2981b145c756db649f3972d913c32"
],
"sequence": 4294967295
}
],
"vout": [
{
"value": 5.0E-5,
"n": 0,
"scriptPubKey": {
"asm": "OP_HASH160 4c0e4893237f85479f489b32c8ff0faf3ee2e1c9 OP_EQUAL",
"hex": "a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987",
"reqSigs": 1,
"type": "scripthash",
"addresses": [
"2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w"
]
}
},
{
"value": 0.01605126,
"n": 1,
"scriptPubKey": {
"asm": "0 f6da24d081a1b63dcacf4a5762a8ed91fd472c68",
"hex": "0014f6da24d081a1b63dcacf4a5762a8ed91fd472c68",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"bcrt1q7mdzf5yp5xmrmjk0fftk928dj875wtrgkwqr5x"
]
}
},
{
"value": 0.12153947,
"n": 2,
"scriptPubKey": {
"asm": "0 4755561caa18d651bf59912545764811d0ab96f6",
"hex": "00144755561caa18d651bf59912545764811d0ab96f6",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"bcrt1qga24v892rrt9r06ejyj52ajgz8g2h9hkwquzg4"
]
}
}
],
"hex": "0100000000010133ba0d88494567a02bffc406b31bd5eb29f0b536a96326baca349b3a96f241e90200000000ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987067e180000000000160014f6da24d081a1b63dcacf4a5762a8ed91fd472c685b74b900000000001600144755561caa18d651bf59912545764811d0ab96f60247304402200d4f214756753861adf835f5d175835d3cd50a19edbf9881572ab7f831a030de0220181b94bbb7d7b0a7a6eee06881a3265cb28405f2d14055e1ff23ac6500471930012102772467db1e5909e7e9f2b169daccf556e7e2981b145c756db649f3972d913c3200000000"
},
{
"txid": "4648da84e1479d222561f72c5211be15510b20129e86c60bdbbdb3e017583a91",
"hash": "16e1ae45d2c6ddaaefeed97a822a95a2933096bf377db147a63d9b730cc30d20",
"version": 1,
"size": 254,
"vsize": 173,
"weight": 689,
"locktime": 0,
"vin": [
{
"txid": "183628048e3b7767e93116c39edc6191311ff3e4666cef10dbda038854c46ee0",
"vout": 2,
"scriptSig": {
"asm": "",
"hex": ""
},
"txinwitness": [
"304402203eeb1713b582be1d74bf6a9f95c573dd41baeedf1efd1bc9a1ad1cccad97c4f70220799a399f53f9325f6cf9681b0138f80bd80f3dc900a4d0ab5cc3c97d5be85f1801",
"0255f56a7be9f88ccf5885ac2f8cd67320424d533d71083270a2891b2488ffb22b"
],
"sequence": 4294967295
}
],
"vout": [
{
"value": 5.0E-5,
"n": 0,
"scriptPubKey": {
"asm": "OP_HASH160 4c0e4893237f85479f489b32c8ff0faf3ee2e1c9 OP_EQUAL",
"hex": "a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987",
"reqSigs": 1,
"type": "scripthash",
"addresses": [
"2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w"
]
}
},
{
"value": 0.0060466,
"n": 1,
"scriptPubKey": {
"asm": "0 5230c895305a232ef2a4feb0a91e7d99e22fd515",
"hex": "00145230c895305a232ef2a4feb0a91e7d99e22fd515",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"bcrt1q2gcv39fstg3jau4yl6c2j8nan83zl4g4w5ch6j"
]
}
},
{
"value": 0.13765586,
"n": 2,
"scriptPubKey": {
"asm": "0 b6fbbd9053e47891fae7f3db7dd3966062b2513c",
"hex": "0014b6fbbd9053e47891fae7f3db7dd3966062b2513c",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"bcrt1qkmammyznu3ufr7h870dhm5ukvp3ty5fudkl63e"
]
}
}
],
"hex": "01000000000101e06ec4548803dadb10ef6c66e4f31f319161dc9ec31631e967773b8e042836180200000000ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987f4390900000000001600145230c895305a232ef2a4feb0a91e7d99e22fd515d20bd20000000000160014b6fbbd9053e47891fae7f3db7dd3966062b2513c0247304402203eeb1713b582be1d74bf6a9f95c573dd41baeedf1efd1bc9a1ad1cccad97c4f70220799a399f53f9325f6cf9681b0138f80bd80f3dc900a4d0ab5cc3c97d5be85f1801210255f56a7be9f88ccf5885ac2f8cd67320424d533d71083270a2891b2488ffb22b00000000"
},
{
"txid": "d21edc2a9ffd0f90982fcdd0ee11a48bc3f78bf437232354f7ad60740a202a14",
"hash": "bae1180680abb989f5b89b5f49729d3eedcf757a12fd0b7649c944a4ba37eaee",
"version": 1,
"size": 255,
"vsize": 173,
"weight": 690,
"locktime": 0,
"vin": [
{
"txid": "4e2146dbaf82ae1d756e6e61df42cbc6db3409d896ecd80894ceb0d3321c704c",
"vout": 2,
"scriptSig": {
"asm": "",
"hex": ""
},
"txinwitness": [
"3045022100ebb8e0ddab46b762e3a9555442cc7ee35c4353d9152e856c97251913902a5056022010d3a0bb51d931a18174dc8ed0ffa37c5ff29f8e924b71d86850de31f3ea4c6e01",
"02e0284cdeae6a8c971e2ea5004ebf9196ee9b3037d6f1ed039c4b5672a69cddc6"
],
"sequence": 4294967295
}
],
"vout": [
{
"value": 5.0E-5,
"n": 0,
"scriptPubKey": {
"asm": "OP_HASH160 4c0e4893237f85479f489b32c8ff0faf3ee2e1c9 OP_EQUAL",
"hex": "a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987",
"reqSigs": 1,
"type": "scripthash",
"addresses": [
"2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w"
]
}
},
{
"value": 0.006,
"n": 1,
"scriptPubKey": {
"asm": "0 89c79bc0628d2d8b1cd91c2ed0e75db13e6f3f3a",
"hex": "001489c79bc0628d2d8b1cd91c2ed0e75db13e6f3f3a",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"bcrt1q38rehsrz35kck8xershdpe6akylx70e63azh0p"
]
}
},
{
"value": 1.11150986,
"n": 2,
"scriptPubKey": {
"asm": "0 062d20a6692350b7a39397c50857a7f725788da0",
"hex": "0014062d20a6692350b7a39397c50857a7f725788da0",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"bcrt1qqckjpfnfydgt0gunjlzss4a87ujh3rdq7u4yrr"
]
}
}
],
"hex": "010000000001014c701c32d3b0ce9408d8ec96d80934dbc6cb42df616e6e751dae82afdb46214e0200000000ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987c02709000000000016001489c79bc0628d2d8b1cd91c2ed0e75db13e6f3f3a8a07a00600000000160014062d20a6692350b7a39397c50857a7f725788da002483045022100ebb8e0ddab46b762e3a9555442cc7ee35c4353d9152e856c97251913902a5056022010d3a0bb51d931a18174dc8ed0ffa37c5ff29f8e924b71d86850de31f3ea4c6e012102e0284cdeae6a8c971e2ea5004ebf9196ee9b3037d6f1ed039c4b5672a69cddc600000000"
},
{
"txid": "78b64023700c50e221d68c093cc3fe32a5314e9696bacb1b6d10217d51664d68",
"hash": "f072bb4fec0ae128ed678d24475cf719c0dd3ba0b44bf14f7300df60408ee365",
"version": 1,
"size": 255,
"vsize": 173,
"weight": 690,
"locktime": 0,
"vin": [
{
"txid": "376a560724612cfc3249e8f93f1a4156fc1b62678716290cbd326e16b509f6c3",
"vout": 2,
"scriptSig": {
"asm": "",
"hex": ""
},
"txinwitness": [
"30450221008f8eee212f209ba2a179197bd4ba35a35ad7a304599025ddbd192a6e7e64c1920220242c297726948ad408ce54c9a0e0287b283c53dc68323537f24a7e3ecd8c526b01",
"03f870bcd3a46e80e4b1236302e62318b412cc97ef096fc976a89deb569dc11ef1"
],
"sequence": 4294967295
}
],
"vout": [
{
"value": 5.0E-5,
"n": 0,
"scriptPubKey": {
"asm": "OP_HASH160 4c0e4893237f85479f489b32c8ff0faf3ee2e1c9 OP_EQUAL",
"hex": "a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987",
"reqSigs": 1,
"type": "scripthash",
"addresses": [
"2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w"
]
}
},
{
"value": 0.016,
"n": 1,
"scriptPubKey": {
"asm": "0 1437b91493d1929b1b42a80e83229c347c28f937",
"hex": "00141437b91493d1929b1b42a80e83229c347c28f937",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"bcrt1qzsmmj9yn6xffkx6z4q8gxg5ux37z37fhr598r9"
]
}
},
{
"value": 1.10145514,
"n": 2,
"scriptPubKey": {
"asm": "0 91ad2cce99e8e4455de5f44559816b98213f3503",
"hex": "001491ad2cce99e8e4455de5f44559816b98213f3503",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"bcrt1qjxkjen5earjy2h0973z4nqttnqsn7dgrnsgy27"
]
}
}
],
"hex": "01000000000101c3f609b5166e32bd0c29168767621bfc56411a3ff9e84932fc2c612407566a370200000000ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987006a1800000000001600141437b91493d1929b1b42a80e83229c347c28f937eaaf90060000000016001491ad2cce99e8e4455de5f44559816b98213f3503024830450221008f8eee212f209ba2a179197bd4ba35a35ad7a304599025ddbd192a6e7e64c1920220242c297726948ad408ce54c9a0e0287b283c53dc68323537f24a7e3ecd8c526b012103f870bcd3a46e80e4b1236302e62318b412cc97ef096fc976a89deb569dc11ef100000000"
},
{
"txid": "dd4243c2743a2d2351814a14628e1976e6fb208e63bd2bbd441180c441205027",
"hash": "dd4243c2743a2d2351814a14628e1976e6fb208e63bd2bbd441180c441205027",
"version": 1,
"size": 252,
"vsize": 252,
"weight": 1008,
"locktime": 0,
"vin": [
{
"txid": "20bd92c145d9d724283c9536355a82e6fd87637963dd7e0c1fb87c9de50a4fba",
"vout": 2,
"scriptSig": {
"asm": "3045022100ba97f6336b3bb3e07cf584010c7b8ab52957e34e462e71252c63f498d51f45b70220708dd78d2d9943f8c176055963ab706870274068fe7b1e5d87592a837336a534[ALL] 039978f14b2463d7d4790cdf2a37c2a3d872dd517ca91db7f6f7a858a7ac661c60",
"hex": "483045022100ba97f6336b3bb3e07cf584010c7b8ab52957e34e462e71252c63f498d51f45b70220708dd78d2d9943f8c176055963ab706870274068fe7b1e5d87592a837336a5340121039978f14b2463d7d4790cdf2a37c2a3d872dd517ca91db7f6f7a858a7ac661c60"
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 5.0E-5,
"n": 0,
"scriptPubKey": {
"asm": "OP_HASH160 4c0e4893237f85479f489b32c8ff0faf3ee2e1c9 OP_EQUAL",
"hex": "a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987",
"reqSigs": 1,
"type": "scripthash",
"addresses": [
"2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w"
]
}
},
{
"value": 0.006,
"n": 1,
"scriptPubKey": {
"asm": "0 13afc3a26c010dddaf2410a9d97b5054a4e8d309",
"hex": "001413afc3a26c010dddaf2410a9d97b5054a4e8d309",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"bcrt1qzwhu8gnvqyxamteyzz5aj76s2jjw35cfctjvy8"
]
}
},
{
"value": 9.45414148,
"n": 2,
"scriptPubKey": {
"asm": "0 5228ee46a95383b314396dda75e707b8bed83034",
"hex": "00145228ee46a95383b314396dda75e707b8bed83034",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"bcrt1q2g5wu34f2wpmx9pedhd8tec8hzldsvp56hljn8"
]
}
}
],
"hex": "0100000001ba4f0ae59d7cb81f0c7edd63796387fde6825a3536953c2824d7d945c192bd20020000006b483045022100ba97f6336b3bb3e07cf584010c7b8ab52957e34e462e71252c63f498d51f45b70220708dd78d2d9943f8c176055963ab706870274068fe7b1e5d87592a837336a5340121039978f14b2463d7d4790cdf2a37c2a3d872dd517ca91db7f6f7a858a7ac661c60ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987c02709000000000016001413afc3a26c010dddaf2410a9d97b5054a4e8d30904e05938000000001600145228ee46a95383b314396dda75e707b8bed8303400000000"
},
{
"txid": "aa33deb87512c2c417a0a9a17c58a36dcf2686fca8b50cc608ad952178a350c2",
"hash": "aa33deb87512c2c417a0a9a17c58a36dcf2686fca8b50cc608ad952178a350c2",
"version": 1,
"size": 251,
"vsize": 251,
"weight": 1004,
"locktime": 0,
"vin": [
{
"txid": "7ac20c807aac72bc1cc57ef25f61567f74a031952b0a18618bc06be504dc715d",
"vout": 2,
"scriptSig": {
"asm": "3044022073a8a8ee9cc490093e6de5708b3727cef35f41038713fab9a5c235b4b400b73102206f97f4fc8faefb534e85c0f3773f2d67781c9871f681211276619054fc540152[ALL] 0374e07f24beca2270cf305100652149a64c80d76611f775ec276658aeae4ef0b5",
"hex": "473044022073a8a8ee9cc490093e6de5708b3727cef35f41038713fab9a5c235b4b400b73102206f97f4fc8faefb534e85c0f3773f2d67781c9871f681211276619054fc54015201210374e07f24beca2270cf305100652149a64c80d76611f775ec276658aeae4ef0b5"
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 5.0E-5,
"n": 0,
"scriptPubKey": {
"asm": "OP_HASH160 4c0e4893237f85479f489b32c8ff0faf3ee2e1c9 OP_EQUAL",
"hex": "a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987",
"reqSigs": 1,
"type": "scripthash",
"addresses": [
"2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w"
]
}
},
{
"value": 0.016,
"n": 1,
"scriptPubKey": {
"asm": "0 1c397ba7ea8410dbd556c51f8184a14ab0151143",
"hex": "00141c397ba7ea8410dbd556c51f8184a14ab0151143",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"bcrt1qrsuhhfl2ssgdh42kc50crp9pf2cp2y2ruplr8l"
]
}
},
{
"value": 1.1126122,
"n": 2,
"scriptPubKey": {
"asm": "0 c38c3d890c415f2c13f6925c1ad1d4a7cb4f7dfe",
"hex": "0014c38c3d890c415f2c13f6925c1ad1d4a7cb4f7dfe",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"bcrt1qcwxrmzgvg90jcylkjfwp45w55l957l07mtsydu"
]
}
}
],
"hex": "01000000015d71dc04e56bc08b61180a2b9531a0747f56615ff27ec51cbc72ac7a800cc27a020000006a473044022073a8a8ee9cc490093e6de5708b3727cef35f41038713fab9a5c235b4b400b73102206f97f4fc8faefb534e85c0f3773f2d67781c9871f681211276619054fc54015201210374e07f24beca2270cf305100652149a64c80d76611f775ec276658aeae4ef0b5ffffffff03881300000000000017a9144c0e4893237f85479f489b32c8ff0faf3ee2e1c987006a1800000000001600141c397ba7ea8410dbd556c51f8184a14ab015114324b6a10600000000160014c38c3d890c415f2c13f6925c1ad1d4a7cb4f7dfe00000000"
},
{
"txid": "719606705c6832f7180ab9db5e1ce6a51bad80a5cbf3b57b806dd10f3d7d5124",
"hash": "c670f473daee2ad2fabc2b8cc9e99d499c19ef9f2d3331345674a0d3b1b96639",
"version": 1,
"size": 425,
"vsize": 263,
"weight": 1052,
"locktime": 0,
"vin": [
{
"txid": "d21edc2a9ffd0f90982fcdd0ee11a48bc3f78bf437232354f7ad60740a202a14",
"vout": 1,
"scriptSig": {
"asm": "",
"hex": ""
},
"txinwitness": [
"30440220073a37eb4371dc3d0cf218d6e9b8e6044275acd07402ccebdf24f65b60a3c1f70220647e71c173f992fc0c5ec6c2b0b1653a95118098269e318c41d1bc33da3ff14f01",
"03c3e858472f39d31c6defdf38b4778660501f0ccfa524b3dd8ba61117b7646635"
],
"sequence": 4294967295
},
{
"txid": "f52b22f8c5156fb51122a90b8dc39989c6912c02a69e22bd3148b0692a7cd699",
"vout": 1,
"scriptSig": {
"asm": "",
"hex": ""
},
"txinwitness": [
"304402202b8ef5de1c56328d3797265272540a054fc04c158b23ee6385b69b14486422c10220749f591fcf4ef995df8f1e7d9aa3cf0c045f1616386b86b419197b360c871fca01",
"02c73e60f00bc72b56568a9f371b9122b3ee29d41730670e98ff8da58e7bbfab28"
],
"sequence": 4294967295
}
],
"vout": [
{
"value": 1.0E-5,
"n": 0,
"scriptPubKey": {
"asm": "0 223d978073802f79e6ecdc7591e5dc1f0ea7030d6466f73c6b90391bc72e886f",
"hex": "0020223d978073802f79e6ecdc7591e5dc1f0ea7030d6466f73c6b90391bc72e886f",
"reqSigs": 1,
"type": "witness_v0_scripthash",
"addresses": [
"bcrt1qyg7e0qrnsqhhnehvm36erewuru82wqcdv3n0w0rtjqu3h3ew3phs27rfy6"
]
}
},
{
"value": 0.0,
"n": 1,
"scriptPubKey": {
"asm": "OP_RETURN 758e9207848c631c6839b1382bb22a52b6ef0645d733389d7be2efb1e8b71454",
"hex": "6a20758e9207848c631c6839b1382bb22a52b6ef0645d733389d7be2efb1e8b71454",
"type": "nulldata"
}
},
{
"value": 0.02201563,
"n": 2,
"scriptPubKey": {
"asm": "0 5e41d2fb8de1c250410416d8dd153c685d3f9c7b",
"hex": "00145e41d2fb8de1c250410416d8dd153c685d3f9c7b",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"bcrt1qteqa97udu8p9qsgyzmvd69fudpwnl8rmt8putj"
]
}
}
],
"hex": "01000000000102142a200a7460adf754232337f48bf7c38ba411eed0cd2f98900ffd9f2adc1ed20100000000ffffffff99d67c2a69b04831bd229ea6022c91c68999c38d0ba92211b56f15c5f8222bf50100000000ffffffff03e803000000000000220020223d978073802f79e6ecdc7591e5dc1f0ea7030d6466f73c6b90391bc72e886f0000000000000000226a20758e9207848c631c6839b1382bb22a52b6ef0645d733389d7be2efb1e8b71454db972100000000001600145e41d2fb8de1c250410416d8dd153c685d3f9c7b024730440220073a37eb4371dc3d0cf218d6e9b8e6044275acd07402ccebdf24f65b60a3c1f70220647e71c173f992fc0c5ec6c2b0b1653a95118098269e318c41d1bc33da3ff14f012103c3e858472f39d31c6defdf38b4778660501f0ccfa524b3dd8ba61117b76466350247304402202b8ef5de1c56328d3797265272540a054fc04c158b23ee6385b69b14486422c10220749f591fcf4ef995df8f1e7d9aa3cf0c045f1616386b86b419197b360c871fca012102c73e60f00bc72b56568a9f371b9122b3ee29d41730670e98ff8da58e7bbfab2800000000"
},
{
"txid": "16cd3283068d2965f26045e15a285e39a762af32ec388ae8c28fb6cb2c468768",
"hash": "a854736ce90b603599bf21be28bea909c3ffafaed57d5eacea1f1730b68db0d8",
"version": 1,
"size": 427,
"vsize": 264,
"weight": 1054,
"locktime": 0,
"vin": [
{
"txid": "4648da84e1479d222561f72c5211be15510b20129e86c60bdbbdb3e017583a91",
"vout": 1,
"scriptSig": {
"asm": "",
"hex": ""
},
"txinwitness": [
"3045022100c9665b9abe7fcab10f775eeafc2391c1fee84c50b50df6d697b1db9a7eea5dd3022070cb7aa57b8f5bf9f2eff11263cf2ea871ab7b9ddfc8e47671cee50ada54724301",
"02016f9a6cc454bd1e74c28df36a079231a215812c60581d1e1745e650f82bd123"
],
"sequence": 4294967295
},
{
"txid": "78b64023700c50e221d68c093cc3fe32a5314e9696bacb1b6d10217d51664d68",
"vout": 1,
"scriptSig": {
"asm": "",
"hex": ""
},
"txinwitness": [
"304502210094cdec8e08f32919b3f25c8672041305c848b4206256ad64f7090dc97dfd1bf002205c397a310cebc690fac04d13945e012d0031246d4574e92f97e3701ca729b61401",
"029b739486d7cf402b3db3913187fad7897e8a5ec3cd8607e9b5fc54a71958b031"
],
"sequence": 4294967295
}
],
"vout": [
{
"value": 1.0E-5,
"n": 0,
"scriptPubKey": {
"asm": "0 e3e81046fd9659b5725736efa404bc1c8f9b2ff6f0af7cb7ddacdcc61e1c7231",
"hex": "0020e3e81046fd9659b5725736efa404bc1c8f9b2ff6f0af7cb7ddacdcc61e1c7231",
"reqSigs": 1,
"type": "witness_v0_scripthash",
"addresses": [
"bcrt1qu05pq3hajevm2ujhxmh6gp9urj8ektlk7zhhed7a4nwvv8suwgcstjh7k7"
]
}
},
{
"value": 0.0,
"n": 1,
"scriptPubKey": {
"asm": "OP_RETURN c63aacf2e8be20752b6f689c0308967cbc335641f2948a4a7962fdde6c464730",
"hex": "6a20c63aacf2e8be20752b6f689c0308967cbc335641f2948a4a7962fdde6c464730",
"type": "nulldata"
}
},
{
"value": 0.0220133,
"n": 2,
"scriptPubKey": {
"asm": "0 5e41d2fb8de1c250410416d8dd153c685d3f9c7b",
"hex": "00145e41d2fb8de1c250410416d8dd153c685d3f9c7b",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"bcrt1qteqa97udu8p9qsgyzmvd69fudpwnl8rmt8putj"
]
}
}
],
"hex": "01000000000102913a5817e0b3bddb0bc6869e12200b5115be11522cf76125229d47e184da48460100000000ffffffff684d66517d21106d1bcbba96964e31a532fec33c098cd621e2500c702340b6780100000000ffffffff03e803000000000000220020e3e81046fd9659b5725736efa404bc1c8f9b2ff6f0af7cb7ddacdcc61e1c72310000000000000000226a20c63aacf2e8be20752b6f689c0308967cbc335641f2948a4a7962fdde6c464730f2962100000000001600145e41d2fb8de1c250410416d8dd153c685d3f9c7b02483045022100c9665b9abe7fcab10f775eeafc2391c1fee84c50b50df6d697b1db9a7eea5dd3022070cb7aa57b8f5bf9f2eff11263cf2ea871ab7b9ddfc8e47671cee50ada547243012102016f9a6cc454bd1e74c28df36a079231a215812c60581d1e1745e650f82bd1230248304502210094cdec8e08f32919b3f25c8672041305c848b4206256ad64f7090dc97dfd1bf002205c397a310cebc690fac04d13945e012d0031246d4574e92f97e3701ca729b6140121029b739486d7cf402b3db3913187fad7897e8a5ec3cd8607e9b5fc54a71958b03100000000"
}
],
"time": 1605510591,
"mediantime": 1589548514,
"nonce": 1,
"bits": "207fffff",
"difficulty": 4.656542373906925E-10,
"chainwork": "0000000000000000000000000000000000000000000000000000000000000118",
"nTx": 11,
"previousblockhash": "1a477356be8211dd4b3a4162521331a6af9b5999401aad8d147a56a1f28c3da3",
"nextblockhash": "7d8267341a57f1f626b450eb22b2dbf208f13ec176e1cc015aa4d2b2ea55016d"
}

View file

@ -0,0 +1,52 @@
{
"version": 210000,
"subversion": "/Satoshi:0.21.0/",
"protocolversion": 70016,
"localservices": "000000000100040d",
"localservicesnames": [
"NETWORK",
"BLOOM",
"WITNESS",
"NETWORK_LIMITED",
"MY_CUSTOM_SERVICE"
],
"localrelay": true,
"timeoffset": -2,
"networkactive": true,
"connections": 9,
"connections_in": 0,
"connections_out": 9,
"networks": [
{
"name": "ipv4",
"limited": false,
"reachable": true,
"proxy": "",
"proxy_randomize_credentials": false
},
{
"name": "ipv6",
"limited": false,
"reachable": true,
"proxy": "",
"proxy_randomize_credentials": false
},
{
"name": "onion",
"limited": true,
"reachable": false,
"proxy": "",
"proxy_randomize_credentials": false
}
],
"relayfee": 1.0E-5,
"incrementalfee": 1.0E-5,
"localaddresses": [
{
"address": "2001:0:2851:782c:2c65:3676:fde6:18f8",
"port": 8333,
"score": 26
}
],
"warnings": ""
}

View file

@ -0,0 +1,10 @@
{
"37fba8bf119c289481eef031c0a35e126376f71d13d7cce35eb0d5e05799b5da": "hUWPf,37fba8bf119c289481eef031c0a35e126376f71d13d7cce35eb0d5e05799b5da,19910000,200,0,668994, tx_missing_from_blockchain_for_4_days",
"b3bc726aa2aa6533cb1e61901ce351eecde234378fe650aee267388886aa6e4b": "ebdttmzh,b3bc726aa2aa6533cb1e61901ce351eecde234378fe650aee267388886aa6e4b,4000000,5000,1,669137, tx_missing_from_blockchain_for_2_days",
"10f32fe53081466f003185a9ef0324d6cbe3f59334ee9ccb2f7155cbfad9c1de": "kmbyoexc,10f32fe53081466f003185a9ef0324d6cbe3f59334ee9ccb2f7155cbfad9c1de,33000000,332,0,668954, tx_not_found",
"cd99836ac4246c3e3980edf95773060481ce52271b74dadeb41e18c42ed21188": "nlaIlAvE,cd99836ac4246c3e3980edf95773060481ce52271b74dadeb41e18c42ed21188,5000000,546,1,669262, invalid_missing_fee_address",
"fc3cb16293895fea8ea5d2d8ab4e39d1b27f583e2c160468b586789a861efa74": "feescammer,fc3cb16293895fea8ea5d2d8ab4e39d1b27f583e2c160468b586789a861efa74,1000000,546,1,669442, invalid_missing_fee_address",
"72cabb5c323c923b43c7f6551974f591dcee148778ee34f9131011ea0ca82813": "PBFICEAS,72cabb5c323c923b43c7f6551974f591dcee148778ee34f9131011ea0ca82813,2000000,546,1,672969, dust_fee_scammer",
"1c8e4934f93b5bbd2823318d5d491698316216f2e4bc0d7cd353f6b16358d80e": "feescammer,1c8e4934f93b5bbd2823318d5d491698316216f2e4bc0d7cd353f6b16358d80e,2000000,546,1,669227, dust_fee_scammer",
"17cbd95d8809dc8808a5c209208f59c4a80e09e012e62951668d30d716c44a96": "feescammer,17cbd95d8809dc8808a5c209208f59c4a80e09e012e62951668d30d716c44a96,2000000,546,1,669340, dust_fee_scammer"
}

View file

@ -0,0 +1,22 @@
{
"4cdea8872a7d96210f378e0221dc1aae8ee9abb282582afa7546890fb39b7189": "am7DzIv,4cdea8872a7d96210f378e0221dc1aae8ee9abb282582afa7546890fb39b7189,6100000,35,0,668195, underpaid but accepted due to use of different DAO parameter",
"ef1ea38b46402deb7df08c13a6dc379a65542a6940ac9d4ba436641ffd4bcb6e": "FQ0A7G,ef1ea38b46402deb7df08c13a6dc379a65542a6940ac9d4ba436641ffd4bcb6e,15970000,92,0,640438, underpaid but accepted due to use of different DAO parameter",
"051770f8d7f43a9b6ca10fefa6cdf4cb124a81eed26dc8af2e40f57d2589107b": "046698,051770f8d7f43a9b6ca10fefa6cdf4cb124a81eed26dc8af2e40f57d2589107b,15970000,92,0,667927, bsq fee underpaid using 5.75 rate for some weird reason",
"e125fdbd09ee86c01e16e1f12a31507cfb8703ed1bd5a221461adf33cb3e00d9": "7213472,e125fdbd09ee86c01e16e1f12a31507cfb8703ed1bd5a221461adf33cb3e00d9,200000000,200000,1,578672, unknown_fee_receiver_1PUXU1MQ",
"44b00de808d0145f9a948fe1b020c5d4173402ba0b5a5ba69124c67e371bca18": "aAPLmh98,44b00de808d0145f9a948fe1b020c5d4173402ba0b5a5ba69124c67e371bca18,140000000,140000,1,578629, unknown_fee_receiver_1PUXU1MQ",
"654a7a34321b57be6a553052d1d9e0f1764dd2fab7b64c9422e9953e4d9d127d": "pwdbdku,654a7a34321b57be6a553052d1d9e0f1764dd2fab7b64c9422e9953e4d9d127d,24980000,238000,1,554947, unknown_fee_receiver_18GzH11",
"0636bafb14890edfb95465e66e2b1e15915f7fb595f9b653b9129c15ef4c1c4b": "msimscqb,0636bafb14890edfb95465e66e2b1e15915f7fb595f9b653b9129c15ef4c1c4b,1000000,10,0,662390",
"2861f4526f40686d5cddc364035b561c13625996233a8b8705195041504ba3a1": "89284,2861f4526f40686d5cddc364035b561c13625996233a8b8705195041504ba3a1,900000,9,0,666473",
"a571e8a2e9227025f897017db0a7cbd7baea98d7f119aea49c46d6535d79caba": "EHGVHSL,a571e8a2e9227025f897017db0a7cbd7baea98d7f119aea49c46d6535d79caba,1000000,5000,1,665825",
"ac001c7eff1cfaaf45f955a9a353f113153cd21610e5e8449b15559592b25d6e": "M2CNGNN,ac001c7eff1cfaaf45f955a9a353f113153cd21610e5e8449b15559592b25d6e,600000,6,0,669043",
"cdd49f58806253abfa6f6566d0659c2f51c28256ef19acdde6a23331d6f07348": "qHBsg,cdd49f58806253abfa6f6566d0659c2f51c28256ef19acdde6a23331d6f07348,25840000,258,0,611324",
"aaf29059ba14264d9fa85fe6700c13b36b3b4aa2748745eafefabcf276dc2c25": "87822,aaf29059ba14264d9fa85fe6700c13b36b3b4aa2748745eafefabcf276dc2c25,1000000,10,0,668839",
"9ab825e4eb298ceb74237faf576c0f5088430fdf05e61a4e9ae4028508b318e6": "9134295,9ab825e4eb298ceb74237faf576c0f5088430fdf05e61a4e9ae4028508b318e6,30000000,30000,1,666606",
"768df499434d48f6dc3329e0abfd3bbc930b884b2caff9b7e4d7f1ec15c4c28d": "5D4EQC,768df499434d48f6dc3329e0abfd3bbc930b884b2caff9b7e4d7f1ec15c4c28d,10000000,101,0,668001",
"9b517de9ef9c00a779b58271a301347e13fc9525be42100452f462526a6f8523": "23608,9b517de9ef9c00a779b58271a301347e13fc9525be42100452f462526a6f8523,5000000,5000,1,668593",
"02f8976ca80f98f095c5656675aa6f40aafced65451917443c1e5057186f2592": "I3WzjuF,02f8976ca80f98f095c5656675aa6f40aafced65451917443c1e5057186f2592,1000000,10,0,666563",
"995de91e69e2590aff67ae6e4f2d417bad6882b11cc095b2420fef7506209be8": "WlvThoI,995de91e69e2590aff67ae6e4f2d417bad6882b11cc095b2420fef7506209be8,1000000,5000,1,669231",
"ca4a1f991c3f585e4fbbb5b5aeb0766ba3eb46bb1c3ff3714db7a8cadd0e557d": "ffhpgz0z,ca4a1f991c3f585e4fbbb5b5aeb0766ba3eb46bb1c3ff3714db7a8cadd0e557d,2000000,20,0,667351",
"b9e1a791f4091910caeb70d1a5d56452bc9614c16b5b74281b2485551faeb46e": "jgtwzsn,b9e1a791f4091910caeb70d1a5d56452bc9614c16b5b74281b2485551faeb46e,1000000,10,0,666372",
"dc06cd41f4b778553a0a5df4578c62eeb0c9c878b6f1a24c60b619a6749877c7": "AZhkSO,dc06cd41f4b778553a0a5df4578c62eeb0c9c878b6f1a24c60b619a6749877c7,200000000,200000,1,668526"
}

View file

@ -0,0 +1,27 @@
{
"44b00de808d0145f9a948fe1b020c5d4173402ba0b5a5ba69124c67e371bca18": "{\"txid\":\"44b00de808d0145f9a948fe1b020c5d4173402ba0b5a5ba69124c67e371bca18\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":1,\"prevout\":{\"value\":147186800}}],\"vout\":[{\"scriptpubkey_address\":\"1PUXU1MQ82JC3Hx1NN5tZs3BaTAJVg72MC\",\"value\":140000},{\"scriptpubkey_address\":\"1HwN7DhxNQdFKzMbrQq5vRHzY4xXGTRcne\",\"value\":147000000}],\"size\":226,\"weight\":904,\"fee\":46800,\"status\":{\"confirmed\":true,\"block_height\":578630}}",
"2861f4526f40686d5cddc364035b561c13625996233a8b8705195041504ba3a1": "{\"txid\":\"2861f4526f40686d5cddc364035b561c13625996233a8b8705195041504ba3a1\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":1393}},{\"vout\":1,\"prevout\":{\"value\":600000}},{\"vout\":2,\"prevout\":{\"value\":10872788}}],\"vout\":[{\"scriptpubkey_address\":\"1NsTgbTUKhveanGCmsawJKLf6asQhJP4p2\",\"value\":1384},{\"scriptpubkey_address\":\"bc1qlw44hxyqfwcmcuuvtktduhth5ah4djl63sc4eq\",\"value\":1500000},{\"scriptpubkey_address\":\"bc1qyty4urzh25j5qypqu7v9mzhwt3p0zvaxeehpxp\",\"value\":9967337}],\"size\":552,\"weight\":1557,\"fee\":5460,\"status\":{\"confirmed\":true,\"block_height\":666479}}",
"0636bafb14890edfb95465e66e2b1e15915f7fb595f9b653b9129c15ef4c1c4b": "{\"txid\":\"0636bafb14890edfb95465e66e2b1e15915f7fb595f9b653b9129c15ef4c1c4b\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":7899}},{\"vout\":2,\"prevout\":{\"value\":54877439}}],\"vout\":[{\"scriptpubkey_address\":\"1FCUu7hqKCSsGhVJaLbGEoCWdZRJRNqq8w\",\"value\":7889},{\"scriptpubkey_address\":\"bc1qkj5l4wxl00ufdx6ygcnrck9fz5u927gkwqcgey\",\"value\":1600000},{\"scriptpubkey_address\":\"bc1qkw4a8u9l5w9fhdh3ue9v7e7celk4jyudzg5gk5\",\"value\":53276799}],\"size\":405,\"weight\":1287,\"fee\":650,\"status\":{\"confirmed\":true,\"block_height\":663140}}",
"a571e8a2e9227025f897017db0a7cbd7baea98d7f119aea49c46d6535d79caba": "{\"txid\":\"a571e8a2e9227025f897017db0a7cbd7baea98d7f119aea49c46d6535d79caba\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":2,\"prevout\":{\"value\":798055}}],\"vout\":[{\"scriptpubkey_address\":\"38bZBj5peYS3Husdz7AH3gEUiUbYRD951t\",\"value\":5000},{\"scriptpubkey_address\":\"bc1qy69ekanm2twzqqr7vz9qcxypyta29wdm2t0ay8\",\"value\":600000},{\"scriptpubkey_address\":\"bc1qp6q2urrntp8tq67lhymftsq0dpqvqmpnus7hym\",\"value\":184830}],\"size\":254,\"weight\":689,\"fee\":8225,\"status\":{\"confirmed\":true,\"block_height\":665826}}",
"ac001c7eff1cfaaf45f955a9a353f113153cd21610e5e8449b15559592b25d6e": "{\"txid\":\"ac001c7eff1cfaaf45f955a9a353f113153cd21610e5e8449b15559592b25d6e\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":4165}},{\"vout\":2,\"prevout\":{\"value\":593157}},{\"vout\":2,\"prevout\":{\"value\":595850}}],\"vout\":[{\"scriptpubkey_address\":\"16Y1WqYEbWygHz6kuhJxWXos3bw46JNNoZ\",\"value\":4159},{\"scriptpubkey_address\":\"bc1qkxjvjp2hyegjpw5jtlju7fcr7pv9en3u7cg7q7\",\"value\":600000},{\"scriptpubkey_address\":\"bc1q9x95y8ktsxg9jucky66da3v2s2har56cy3nkkg\",\"value\":575363}],\"size\":555,\"weight\":1563,\"fee\":13650,\"status\":{\"confirmed\":true,\"block_height\":669045}}",
"cdd49f58806253abfa6f6566d0659c2f51c28256ef19acdde6a23331d6f07348": "{\"txid\":\"cdd49f58806253abfa6f6566d0659c2f51c28256ef19acdde6a23331d6f07348\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":4898}},{\"vout\":2,\"prevout\":{\"value\":15977562}}],\"vout\":[{\"scriptpubkey_address\":\"16SCUfnCLddxgoAYLUcsJcoE4VxBRzgTSz\",\"value\":4640},{\"scriptpubkey_address\":\"1N9Pb6DTJXh96QjzYLDFTZuBvFXgFPi18N\",\"value\":1292000},{\"scriptpubkey_address\":\"1C7tg4KT9wQvLR5xfPDqD4U35Ncwk3UQxm\",\"value\":14681720}],\"size\":406,\"weight\":1624,\"fee\":4100,\"status\":{\"confirmed\":true,\"block_height\":611325}}",
"aaf29059ba14264d9fa85fe6700c13b36b3b4aa2748745eafefabcf276dc2c25": "{\"txid\":\"aaf29059ba14264d9fa85fe6700c13b36b3b4aa2748745eafefabcf276dc2c25\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":3512}},{\"vout\":2,\"prevout\":{\"value\":1481349}},{\"vout\":1,\"prevout\":{\"value\":600000}}],\"vout\":[{\"scriptpubkey_address\":\"14rNP2aC23hr6u8ALmksm3RgJys7CAD3No\",\"value\":3502},{\"scriptpubkey_address\":\"bc1qvctcjcrhznptmydv4hxwc4wd2km76shkl3jj29\",\"value\":1600000},{\"scriptpubkey_address\":\"bc1qsdzpvr6sehypswcwjsmmjzctjhy5hkwqvf2vh8\",\"value\":476289}],\"size\":555,\"weight\":1563,\"fee\":5070,\"status\":{\"confirmed\":true,\"block_height\":668841}}",
"9ab825e4eb298ceb74237faf576c0f5088430fdf05e61a4e9ae4028508b318e6": "{\"txid\":\"9ab825e4eb298ceb74237faf576c0f5088430fdf05e61a4e9ae4028508b318e6\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":4533500}}],\"vout\":[{\"scriptpubkey_address\":\"1EKXx73oUhHaUh8JBimtiPGgHfwNmxYKAj\",\"value\":30000},{\"scriptpubkey_address\":\"bc1qde7asrvrnkn5st5q8u038fxt9tlrgyaxwju6hn\",\"value\":4500000}],\"size\":226,\"weight\":574,\"fee\":3500,\"status\":{\"confirmed\":true,\"block_height\":666607}}",
"768df499434d48f6dc3329e0abfd3bbc930b884b2caff9b7e4d7f1ec15c4c28d": "{\"txid\":\"768df499434d48f6dc3329e0abfd3bbc930b884b2caff9b7e4d7f1ec15c4c28d\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":249699}},{\"vout\":1,\"prevout\":{\"value\":4023781}}],\"vout\":[{\"scriptpubkey_address\":\"1J8wtmJuurfBSrRb27urtoHzuQey1ipXPX\",\"value\":249598},{\"scriptpubkey_address\":\"bc1qfmyw7pwaqucprcsauqr6gvez9wep290r4amd3y\",\"value\":1500000},{\"scriptpubkey_address\":\"bc1q2lx0fymd3mmk4pzjq2k8hn7mk3hnctnjtu497t\",\"value\":2517382}],\"size\":405,\"weight\":1287,\"fee\":6500,\"status\":{\"confirmed\":true,\"block_height\":668002}}",
"02f8976ca80f98f095c5656675aa6f40aafced65451917443c1e5057186f2592": "{\"txid\":\"02f8976ca80f98f095c5656675aa6f40aafced65451917443c1e5057186f2592\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":33860}},{\"vout\":1,\"prevout\":{\"value\":600000}},{\"vout\":2,\"prevout\":{\"value\":39304}},{\"vout\":3,\"prevout\":{\"value\":5000000}}],\"vout\":[{\"scriptpubkey_address\":\"1Le1auzXSpEnyMc6S9KNentnye3gTPLnuA\",\"value\":33850},{\"scriptpubkey_address\":\"bc1qs73jfmjzclsx9466pvpslfuqc2kkv5uc8u928a\",\"value\":1600000},{\"scriptpubkey_address\":\"bc1q85zlv50mddyuerze7heve0vcv4f80qsw2szv34\",\"value\":4031088}],\"size\":701,\"weight\":1829,\"fee\":8226,\"status\":{\"confirmed\":true,\"block_height\":666564}}",
"9b517de9ef9c00a779b58271a301347e13fc9525be42100452f462526a6f8523": "{\"txid\":\"9b517de9ef9c00a779b58271a301347e13fc9525be42100452f462526a6f8523\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":2,\"prevout\":{\"value\":4466600}}],\"vout\":[{\"scriptpubkey_address\":\"38bZBj5peYS3Husdz7AH3gEUiUbYRD951t\",\"value\":5000},{\"scriptpubkey_address\":\"bc1qrl0dvwp6hpqlcj65qfhl70lz67yjvhlc8z73a4\",\"value\":750000},{\"scriptpubkey_address\":\"bc1qg55gnkhgg4zltdh76sdef33xzr7h95g3xsxesg\",\"value\":3709675}],\"size\":254,\"weight\":689,\"fee\":1925,\"status\":{\"confirmed\":true,\"block_height\":668843}}",
"995de91e69e2590aff67ae6e4f2d417bad6882b11cc095b2420fef7506209be8": "{\"txid\":\"995de91e69e2590aff67ae6e4f2d417bad6882b11cc095b2420fef7506209be8\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":2,\"prevout\":{\"value\":36006164}}],\"vout\":[{\"scriptpubkey_address\":\"19qA2BVPoyXDfHKVMovKG7SoxGY7xrBV8c\",\"value\":5000},{\"scriptpubkey_address\":\"bc1qefxyxsq9tskaw0qarxf0hdxusxe64l8zsmsgrz\",\"value\":1600000},{\"scriptpubkey_address\":\"bc1q6fky0fxcg3zrz5t0xdyq5sh90h7m5sya0wf9gx\",\"value\":34390664}],\"size\":256,\"weight\":697,\"fee\":10500,\"status\":{\"confirmed\":true,\"block_height\":669233}}",
"fc3cb16293895fea8ea5d2d8ab4e39d1b27f583e2c160468b586789a861efa74": "{\"txid\":\"fc3cb16293895fea8ea5d2d8ab4e39d1b27f583e2c160468b586789a861efa74\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":1,\"prevout\":{\"value\":49144}},{\"vout\":0,\"prevout\":{\"value\":750000}}],\"vout\":[{\"scriptpubkey_address\":\"bc1qgsx9y62ajme3gg8v9n9jfps2694uy9r6f9unj0\",\"value\":600000},{\"scriptpubkey_address\":\"bc1q6lqf0jehmaadwmdhap98rulflft27z00g0qphn\",\"value\":187144}],\"size\":372,\"weight\":834,\"fee\":12000,\"status\":{\"confirmed\":true,\"block_height\":669442}}",
"ca4a1f991c3f585e4fbbb5b5aeb0766ba3eb46bb1c3ff3714db7a8cadd0e557d": "{\"txid\":\"ca4a1f991c3f585e4fbbb5b5aeb0766ba3eb46bb1c3ff3714db7a8cadd0e557d\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":10237}},{\"vout\":1,\"prevout\":{\"value\":600000}},{\"vout\":1,\"prevout\":{\"value\":900000}},{\"vout\":2,\"prevout\":{\"value\":8858290}}],\"vout\":[{\"scriptpubkey_address\":\"1DQXP1AXR1qkXficdkfXHHy2JkbtRGFQ1b\",\"value\":10217},{\"scriptpubkey_address\":\"bc1q9jfjvhvr42smvwylrlqcrefcdxagdzf52aquzm\",\"value\":2600000},{\"scriptpubkey_address\":\"bc1qc6qraj5h8qxvluh2um4rvunqn68fltc9kjfrk9\",\"value\":7753730}],\"size\":702,\"weight\":1833,\"fee\":4580,\"status\":{\"confirmed\":true,\"block_height\":667352}}",
"4cdea8872a7d96210f378e0221dc1aae8ee9abb282582afa7546890fb39b7189": "{\"txid\":\"4cdea8872a7d96210f378e0221dc1aae8ee9abb282582afa7546890fb39b7189\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":23893}},{\"vout\":1,\"prevout\":{\"value\":1440000}},{\"vout\":2,\"prevout\":{\"value\":16390881}}],\"vout\":[{\"scriptpubkey_address\":\"1Kmrzq3WGCQsZw5kroEphuk1KgsEr65yB7\",\"value\":23858},{\"scriptpubkey_address\":\"bc1qyw5qql9m7rkse9mhcun225nrjpwycszsa5dpjg\",\"value\":7015000},{\"scriptpubkey_address\":\"bc1q90y3p6mg0pe3rvvzfeudq4mfxafgpc9rulruff\",\"value\":10774186}],\"size\":554,\"weight\":1559,\"fee\":41730,\"status\":{\"confirmed\":true,\"block_height\":668198}}",
"b9e1a791f4091910caeb70d1a5d56452bc9614c16b5b74281b2485551faeb46e": "{\"txid\":\"b9e1a791f4091910caeb70d1a5d56452bc9614c16b5b74281b2485551faeb46e\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":1432}},{\"vout\":2,\"prevout\":{\"value\":30302311}}],\"vout\":[{\"scriptpubkey_address\":\"16MK64AGvKVF7xu9Xfjh8o7Xo4e1HMhUqq\",\"value\":1422},{\"scriptpubkey_address\":\"bc1qjp535w2zl3cxg02xgdx8yewtvn6twcnj86t73c\",\"value\":1600000},{\"scriptpubkey_address\":\"bc1qa58rfr0wumczmau0qehjwcsdkcgs5dmkg7url5\",\"value\":28698421}],\"size\":405,\"weight\":1287,\"fee\":3900,\"status\":{\"confirmed\":true,\"block_height\":666373}}",
"dc06cd41f4b778553a0a5df4578c62eeb0c9c878b6f1a24c60b619a6749877c7": "{\"txid\":\"dc06cd41f4b778553a0a5df4578c62eeb0c9c878b6f1a24c60b619a6749877c7\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":230000000}}],\"vout\":[{\"scriptpubkey_address\":\"38bZBj5peYS3Husdz7AH3gEUiUbYRD951t\",\"value\":200000},{\"scriptpubkey_address\":\"bc1qaq3v7gjqaeyx7yzkcu59l8f47apkfutr927xa8\",\"value\":30000000},{\"scriptpubkey_address\":\"bc1qx9avgdnkal2jfcfjqdsdu7ly60awl4wcfgk6m0\",\"value\":199793875}],\"size\":255,\"weight\":690,\"fee\":6125,\"status\":{\"confirmed\":true,\"block_height\":668527}}",
"ef1ea38b46402deb7df08c13a6dc379a65542a6940ac9d4ba436641ffd4bcb6e": "{\"txid\":\"ef1ea38b46402deb7df08c13a6dc379a65542a6940ac9d4ba436641ffd4bcb6e\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":61000}},{\"vout\":0,\"prevout\":{\"value\":6415500}}],\"vout\":[{\"scriptpubkey_address\":\"164hNDe95nNsQYVSVbeypn36HqT5uD5AoT\",\"value\":60908},{\"scriptpubkey_address\":\"1MEsN2jLyrcWBMjggSPs88xAnj6D38sQL3\",\"value\":2395500},{\"scriptpubkey_address\":\"1A3pYPW1zQcMpHUnSfPCxYWgCrUW93t2yV\",\"value\":3973352}],\"size\":408,\"weight\":1632,\"fee\":46740,\"status\":{\"confirmed\":true,\"block_height\":640441}}",
"654a7a34321b57be6a553052d1d9e0f1764dd2fab7b64c9422e9953e4d9d127d": "{\"txid\":\"654a7a34321b57be6a553052d1d9e0f1764dd2fab7b64c9422e9953e4d9d127d\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":26000000}}],\"vout\":[{\"scriptpubkey_address\":\"18GzH11T5h2fpvUoBJDub7MgNJVw3FfqQ8\",\"value\":238000},{\"scriptpubkey_address\":\"1JYZ4cba5pjPxXqm5MDGUVvj2k3cZezRaR\",\"value\":3000000},{\"scriptpubkey_address\":\"12DNP86oaEXfEBkow4Kpkw2tNaqoECYhtc\",\"value\":22756800}],\"size\":260,\"weight\":1040,\"fee\":5200,\"status\":{\"confirmed\":true,\"block_height\":554950}}",
"1c8e4934f93b5bbd2823318d5d491698316216f2e4bc0d7cd353f6b16358d80e": "{\"txid\":\"1c8e4934f93b5bbd2823318d5d491698316216f2e4bc0d7cd353f6b16358d80e\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":2,\"prevout\":{\"value\":563209}},{\"vout\":1,\"prevout\":{\"value\":600000}},{\"vout\":2,\"prevout\":{\"value\":214153}},{\"vout\":2,\"prevout\":{\"value\":116517}},{\"vout\":2,\"prevout\":{\"value\":135306}},{\"vout\":2,\"prevout\":{\"value\":261906}},{\"vout\":2,\"prevout\":{\"value\":598038}},{\"vout\":1,\"prevout\":{\"value\":600000}},{\"vout\":1,\"prevout\":{\"value\":600000}},{\"vout\":1,\"prevout\":{\"value\":600932}},{\"vout\":1,\"prevout\":{\"value\":600944}}],\"vout\":[{\"scriptpubkey_address\":\"19qA2BVPoyXDfHKVMovKG7SoxGY7xrBV8c\",\"value\":546},{\"scriptpubkey_address\":\"bc1qwcwu3mx0nmf290y8t0jlukhxujaul0fc4jxe44\",\"value\":4743164},{\"scriptpubkey_address\":\"bc1qduw8nd2sscyezk02xj3a3ks5adh8wmctqaew6g\",\"value\":145131}],\"size\":1746,\"weight\":3417,\"fee\":2164,\"status\":{\"confirmed\":true,\"block_height\":669227}}",
"e125fdbd09ee86c01e16e1f12a31507cfb8703ed1bd5a221461adf33cb3e00d9": "{\"txid\":\"e125fdbd09ee86c01e16e1f12a31507cfb8703ed1bd5a221461adf33cb3e00d9\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":1,\"prevout\":{\"value\":4250960}}],\"vout\":[{\"scriptpubkey_address\":\"1PUXU1MQ82JC3Hx1NN5tZs3BaTAJVg72MC\",\"value\":200000},{\"scriptpubkey_address\":\"1MSkjSzF1dTKR121scX64Brvs4zhExVE8Q\",\"value\":4000000}],\"size\":225,\"weight\":900,\"fee\":50960,\"status\":{\"confirmed\":true,\"block_height\":578733}}",
"051770f8d7f43a9b6ca10fefa6cdf4cb124a81eed26dc8af2e40f57d2589107b": "{\"txid\":\"051770f8d7f43a9b6ca10fefa6cdf4cb124a81eed26dc8af2e40f57d2589107b\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":23985}},{\"vout\":1,\"prevout\":{\"value\":6271500}},{\"vout\":0,\"prevout\":{\"value\":2397000}},{\"vout\":2,\"prevout\":{\"value\":41281331}}],\"vout\":[{\"scriptpubkey_address\":\"16pULNutwpJ5E6EaxopQQDAVaFJXt8B18Z\",\"value\":23893},{\"scriptpubkey_address\":\"bc1q6hkhftt9v5kkcj9wr66ycqy23dqyle3h3wnv50\",\"value\":18365500},{\"scriptpubkey_address\":\"bc1q3ffqm4e4wxdg8jgcw0wlpw4vg9hgwnql3y9zn0\",\"value\":31546169}],\"size\":703,\"weight\":2476,\"fee\":38254,\"status\":{\"confirmed\":true,\"block_height\":667928}}",
"72cabb5c323c923b43c7f6551974f591dcee148778ee34f9131011ea0ca82813": "{\"txid\":\"72cabb5c323c923b43c7f6551974f591dcee148778ee34f9131011ea0ca82813\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":1,\"prevout\":{\"value\":12000000}}],\"vout\":[{\"scriptpubkey_address\":\"3EfRGckBQQuk7cpU7SwatPv8kFD1vALkTU\",\"value\":546},{\"scriptpubkey_address\":\"bc1q6xthjqca0p83mua54e9t0sapxkvc7n3dvwssxc\",\"value\":2600000},{\"scriptpubkey_address\":\"bc1q3uaew9e6uqm6pth8nq7wh3wcwzxwh2q25fggcg\",\"value\":9388079}],\"size\":254,\"weight\":689,\"fee\":11375,\"status\":{\"confirmed\":true,\"block_height\":672972}}",
"17cbd95d8809dc8808a5c209208f59c4a80e09e012e62951668d30d716c44a96": "{\"txid\":\"17cbd95d8809dc8808a5c209208f59c4a80e09e012e62951668d30d716c44a96\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":2,\"prevout\":{\"value\":2810563}}],\"vout\":[{\"scriptpubkey_address\":\"13sxMq8mTw7CTSqgGiMPfwo6ZDsVYrHLmR\",\"value\":546},{\"scriptpubkey_address\":\"bc1qklv4zsl598ujy2ntl5g3wqjxasu2f74egw0tlm\",\"value\":1603262},{\"scriptpubkey_address\":\"bc1qclesyfupj309620thesxmj4vcdscjfykdqz4np\",\"value\":1205124}],\"size\":256,\"weight\":697,\"fee\":1631,\"status\":{\"confirmed\":true,\"block_height\":669340}}",
"cd99836ac4246c3e3980edf95773060481ce52271b74dadeb41e18c42ed21188": "{\"txid\":\"cd99836ac4246c3e3980edf95773060481ce52271b74dadeb41e18c42ed21188\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":2,\"prevout\":{\"value\":200584}},{\"vout\":1,\"prevout\":{\"value\":600000}}],\"vout\":[{\"scriptpubkey_address\":\"bc1q7sd0k2a6p942848y5nsk9cqwdguhd7c04t2t3w\",\"value\":750000},{\"scriptpubkey_address\":\"bc1qrcez45uf02sg6zvk3mqmtlc9vnrvn50jcywlk5\",\"value\":49144}],\"size\":371,\"weight\":833,\"fee\":1440,\"status\":{\"confirmed\":true,\"block_height\":669442}}"
}

View file

@ -0,0 +1,9 @@
# nodeaddress.onion:port [(@owner,@backup)]
5quyxpxheyvzmb2d.onion:8000 (@miker)
s67qglwhkgkyvr74.onion:8000 (@emzy)
ef5qnzx6znifo3df.onion:8000 (@alexej996)
jhgcy2won7xnslrb.onion:8000 (@wiz,@nicolasdorier)
3f3cu2yw7u457ztq.onion:8000 (@devinbileck,@ripcurlx)
723ljisnynbtdohi.onion:8000 (@emzy)
rm7b56wbrcczpjvl.onion:8000 (@miker)
fl3mmribyxgrv63c.onion:8000 (@devinbileck,@ripcurlx)

View file

@ -0,0 +1 @@
mock-maker-inline # enable mocking final classes in mockito

View file

@ -0,0 +1,10 @@
# For development you need to change that to your local onion addresses
# 1. Run a seed node with prog args: --bitcoinNetwork=regtest --nodePort=8002 --appName=bisq_seed_node_rxdkppp3vicnbgqt.onion_8002
# 2. Find your local onion address in bisq_seed_node_rxdkppp3vicnbgqt.onion_8002/regtest/tor/hiddenservice/hostname
# 3. Shut down the seed node
# 4. Rename the directory with your local onion address
# 5. Edit here your found onion address (new NodeAddress("YOUR_ONION.onion:8002")
# nodeaddress.onion:port [(@owner)]
rxdkppp3vicnbgqt.onion:8002
4ie52dse64kaarxw.onion:8002

View file

@ -0,0 +1,7 @@
# nodeaddress.onion:port [(@owner)]
snenz4mea65wigen.onion:8001
fjr5w4eckjghqtnu.onion:8001
3d56s6acbi3vk52v.onion:8001
74w2sttlo4qk6go3.onion:8001
gtif46mfxirv533z.onion:8001
jmc5ajqvtnzqaggm.onion:8001