diff --git a/Dockerfile b/Dockerfile index e99687467d..c7f5575201 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ RUN mvn clean package FROM openjdk:jre-slim WORKDIR /iri -COPY --from=builder /iri/target/iri-1.4.1.1.jar iri.jar +COPY --from=builder /iri/target/iri-1.4.1.2.jar iri.jar COPY logback.xml /iri VOLUME /iri diff --git a/changelog.txt b/changelog.txt index 3641853e71..4e45eb697b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +1.4.1.2 + - Fixes DB over-storing transactions due to concurrency + - Upgrade to RocksDB 5.7.3 + - Upgrades findTransaction logic to match spec (Multi-field intersection, Input field limits) + - Unified API input validation + 1.4.1.1 - Fixes CORS issue introduced with last release - attachToTangle no longer overwrites tag field unconditionally diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index c0782d9fef..50ebf9c6de 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ com.iota iri IRI - 1.4.1 + 1.4.1.2 IOTA Reference Implementation scm:git:git://github.com/iotaledger/iri.git @@ -61,7 +61,7 @@ enforce - verify + package enforce @@ -74,7 +74,7 @@ org.slf4j:slf4j-api:1.7.25:jar:null:compile:da76ca59f6a57ee3102f8f9bd9cee742973efa8a ch.qos.logback:logback-classic:1.2.3:jar:null:compile:7c4f3c474fb2c041d8028740440937705ebb473a commons-io:commons-io:2.5:jar:null:compile:2852e6e05fbb95076fc091f6d1780f1f8fe35e0f - org.rocksdb:rocksdbjni:5.1.4:jar:null:compile:1924f10f010ddf724a41d2ce6903a706bda7f1b6 + org.rocksdb:rocksdbjni:5.7.3:jar:null:compile:421b44ad957a2b6cce5adedc204db551831b553d com.google.code.gson:gson:2.8.1:jar:null:compile:02a8e0aa38a2e21cb39e2f5a7d6704cbdc941da0 io.undertow:undertow-core:${undertow.version}:jar:null:compile:e5764e5017bfe8c2dd421dc80035e5165501bfda io.undertow:undertow-servlet:${undertow.version}:jar:null:compile:0e2850a558e70a2d72d9a3e782c7b6fde2d9f1c7 diff --git a/pom.xml b/pom.xml index 04eba6f128..662ce2f9c2 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.iota iri - 1.4.1.1 + 1.4.1.2 IRI IOTA Reference Implementation @@ -76,7 +76,7 @@ org.rocksdb rocksdbjni - 5.1.4 + 5.7.3 @@ -212,7 +212,7 @@ enforce - verify + package enforce @@ -232,7 +232,7 @@ org.slf4j:slf4j-api:1.7.25:jar:null:compile:da76ca59f6a57ee3102f8f9bd9cee742973efa8a ch.qos.logback:logback-classic:1.2.3:jar:null:compile:7c4f3c474fb2c041d8028740440937705ebb473a commons-io:commons-io:2.5:jar:null:compile:2852e6e05fbb95076fc091f6d1780f1f8fe35e0f - org.rocksdb:rocksdbjni:5.1.4:jar:null:compile:1924f10f010ddf724a41d2ce6903a706bda7f1b6 + org.rocksdb:rocksdbjni:5.7.3:jar:null:compile:421b44ad957a2b6cce5adedc204db551831b553d com.google.code.gson:gson:2.8.1:jar:null:compile:02a8e0aa38a2e21cb39e2f5a7d6704cbdc941da0 io.undertow:undertow-core:${undertow.version}:jar:null:compile:e5764e5017bfe8c2dd421dc80035e5165501bfda io.undertow:undertow-servlet:${undertow.version}:jar:null:compile:0e2850a558e70a2d72d9a3e782c7b6fde2d9f1c7 diff --git a/src/main/java/com/iota/iri/IRI.java b/src/main/java/com/iota/iri/IRI.java index c81cd0a00e..5daaba63c9 100644 --- a/src/main/java/com/iota/iri/IRI.java +++ b/src/main/java/com/iota/iri/IRI.java @@ -40,7 +40,7 @@ public class IRI { public static final String MAINNET_NAME = "IRI"; public static final String TESTNET_NAME = "IRI Testnet"; - public static final String VERSION = "1.4.1.1"; + public static final String VERSION = "1.4.1.2"; public static Iota iota; public static API api; public static IXI ixi; diff --git a/src/main/java/com/iota/iri/conf/Configuration.java b/src/main/java/com/iota/iri/conf/Configuration.java index 848f2f692a..245da0de55 100644 --- a/src/main/java/com/iota/iri/conf/Configuration.java +++ b/src/main/java/com/iota/iri/conf/Configuration.java @@ -55,7 +55,9 @@ public enum DefaultConfSettings { MIN_RANDOM_WALKS, MAX_RANDOM_WALKS, MAX_FIND_TRANSACTIONS, + MAX_REQUESTS_LIST, MAX_GET_TRYTES, + MAX_BODY_LENGTH, MAX_DEPTH, MAINNET_MWM, TESTNET_MWM, @@ -104,7 +106,9 @@ public enum DefaultConfSettings { conf.put(DefaultConfSettings.MAX_DEPTH.name(), "15"); conf.put(DefaultConfSettings.MAX_FIND_TRANSACTIONS.name(), "100000"); + conf.put(DefaultConfSettings.MAX_REQUESTS_LIST.name(), "1000"); conf.put(DefaultConfSettings.MAX_GET_TRYTES.name(), "10000"); + conf.put(DefaultConfSettings.MAX_BODY_LENGTH.name(), "1000000"); conf.put(DefaultConfSettings.ZMQ_ENABLED.name(), "false"); conf.put(DefaultConfSettings.ZMQ_PORT.name(), "5556"); conf.put(DefaultConfSettings.ZMQ_IPC.name(), "ipc://iri"); diff --git a/src/main/java/com/iota/iri/controllers/TransactionViewModel.java b/src/main/java/com/iota/iri/controllers/TransactionViewModel.java index e11118a5a9..4d9c8049c2 100644 --- a/src/main/java/com/iota/iri/controllers/TransactionViewModel.java +++ b/src/main/java/com/iota/iri/controllers/TransactionViewModel.java @@ -55,19 +55,22 @@ public class TransactionViewModel { private int[] trits; public int weightMagnitude; - public static TransactionViewModel find(final Tangle tangle, byte[] hash) throws Exception { - TransactionViewModel transactionViewModel = new TransactionViewModel((Transaction) tangle.find(Transaction.class, hash), new Hash(hash)); - if(!transactionViewModel.getHash().equals(Hash.NULL_HASH) && !transactionViewModel.transaction.parsed) { + public static void fillMetadata(final Tangle tangle, TransactionViewModel transactionViewModel) throws Exception { + if (transactionViewModel.getHash().equals(Hash.NULL_HASH)) { return; } + if(transactionViewModel.getType() == FILLED_SLOT && !transactionViewModel.transaction.parsed) { tangle.saveBatch(transactionViewModel.getMetadataSaveBatch()); } + } + + public static TransactionViewModel find(final Tangle tangle, byte[] hash) throws Exception { + TransactionViewModel transactionViewModel = new TransactionViewModel((Transaction) tangle.find(Transaction.class, hash), new Hash(hash)); + fillMetadata(tangle, transactionViewModel); return transactionViewModel; } public static TransactionViewModel fromHash(final Tangle tangle, final Hash hash) throws Exception { TransactionViewModel transactionViewModel = new TransactionViewModel((Transaction) tangle.load(Transaction.class, hash), hash); - if(!transactionViewModel.getHash().equals(Hash.NULL_HASH) && !transactionViewModel.transaction.parsed) { - tangle.saveBatch(transactionViewModel.getMetadataSaveBatch()); - } + fillMetadata(tangle, transactionViewModel); return transactionViewModel; } @@ -118,7 +121,7 @@ public boolean update(final Tangle tangle, String item) throws Exception { if(hash.equals(Hash.NULL_HASH)) { return false; } - return tangle.update(transaction, getHash(), item); + return tangle.update(transaction, hash, item); } public TransactionViewModel getBranchTransaction(final Tangle tangle) throws Exception { @@ -149,16 +152,16 @@ public synchronized int[] trits() { } public void delete(Tangle tangle) throws Exception { - tangle.delete(Transaction.class, getHash()); + tangle.delete(Transaction.class, hash); } public List> getMetadataSaveBatch() throws Exception { List> hashesList = new ArrayList<>(); - hashesList.add(new Pair<>(getAddressHash(), new Address(getHash()))); - hashesList.add(new Pair<>(getBundleHash(), new Bundle(getHash()))); - hashesList.add(new Pair<>(getBranchTransactionHash(), new Approvee(getHash()))); - hashesList.add(new Pair<>(getTrunkTransactionHash(), new Approvee(getHash()))); - hashesList.add(new Pair<>(getObsoleteTagValue(), new Tag(getHash()))); + hashesList.add(new Pair<>(getAddressHash(), new Address(hash))); + hashesList.add(new Pair<>(getBundleHash(), new Bundle(hash))); + hashesList.add(new Pair<>(getBranchTransactionHash(), new Approvee(hash))); + hashesList.add(new Pair<>(getTrunkTransactionHash(), new Approvee(hash))); + hashesList.add(new Pair<>(getObsoleteTagValue(), new Tag(hash))); setAttachmentData(); setMetadata(); return hashesList; @@ -168,7 +171,7 @@ public List> getSaveBatch() throws Exception { List> hashesList = new ArrayList<>(); hashesList.addAll(getMetadataSaveBatch()); getBytes(); - hashesList.add(new Pair<>(getHash(), transaction)); + hashesList.add(new Pair<>(hash, transaction)); return hashesList; } @@ -182,7 +185,7 @@ public static TransactionViewModel first(Tangle tangle) throws Exception { } public TransactionViewModel next(Tangle tangle) throws Exception { - Pair transactionPair = tangle.next(Transaction.class, getHash()); + Pair transactionPair = tangle.next(Transaction.class, hash); if(transactionPair != null && transactionPair.hi != null) { return new TransactionViewModel((Transaction) transactionPair.hi, (Hash) transactionPair.low); } @@ -190,10 +193,15 @@ public TransactionViewModel next(Tangle tangle) throws Exception { } public boolean store(Tangle tangle) throws Exception { - if(!exists(tangle, getHash()) && !getHash().equals(Hash.NULL_HASH)) { - return tangle.saveBatch(getSaveBatch()); + if (hash.equals(Hash.NULL_HASH) || exists(tangle, hash)) { + return false; } - return false; + + List> batch = getSaveBatch(); + if (exists(tangle, hash)) { + return false; + } + return tangle.saveBatch(batch); } public ApproveeViewModel getApprovers(Tangle tangle) throws Exception { @@ -388,26 +396,26 @@ private void updateHeight(long height) throws Exception { } public void updateHeights(final Tangle tangle) throws Exception { - TransactionViewModel transaction = this, trunk = this.getTrunkTransaction(tangle); + TransactionViewModel transactionVM = this, trunk = this.getTrunkTransaction(tangle); Stack transactionViewModels = new Stack<>(); - transactionViewModels.push(transaction.getHash()); + transactionViewModels.push(transactionVM.getHash()); while(trunk.getHeight() == 0 && trunk.getType() != PREFILLED_SLOT && !trunk.getHash().equals(Hash.NULL_HASH)) { - transaction = trunk; - trunk = transaction.getTrunkTransaction(tangle); - transactionViewModels.push(transaction.getHash()); + transactionVM = trunk; + trunk = transactionVM.getTrunkTransaction(tangle); + transactionViewModels.push(transactionVM.getHash()); } while(transactionViewModels.size() != 0) { - transaction = TransactionViewModel.fromHash(tangle, transactionViewModels.pop()); - if(trunk.getHash().equals(Hash.NULL_HASH) && trunk.getHeight() == 0 && !transaction.getHash().equals(Hash.NULL_HASH)) { - transaction.updateHeight(1L); - transaction.update(tangle, "height"); - } else if ( trunk.getType() != PREFILLED_SLOT && transaction.getHeight() == 0){ - transaction.updateHeight(1 + trunk.getHeight()); - transaction.update(tangle, "height"); + transactionVM = TransactionViewModel.fromHash(tangle, transactionViewModels.pop()); + if(trunk.getHash().equals(Hash.NULL_HASH) && trunk.getHeight() == 0 && !transactionVM.getHash().equals(Hash.NULL_HASH)) { + transactionVM.updateHeight(1L); + transactionVM.update(tangle, "height"); + } else if ( trunk.getType() != PREFILLED_SLOT && transactionVM.getHeight() == 0){ + transactionVM.updateHeight(1 + trunk.getHeight()); + transactionVM.update(tangle, "height"); } else { break; } - trunk = transaction; + trunk = transactionVM; } } diff --git a/src/main/java/com/iota/iri/model/Transaction.java b/src/main/java/com/iota/iri/model/Transaction.java index fd6f4566a7..26647976cd 100644 --- a/src/main/java/com/iota/iri/model/Transaction.java +++ b/src/main/java/com/iota/iri/model/Transaction.java @@ -30,7 +30,7 @@ public class Transaction implements Persistable { public long attachmentTimestampUpperBound; public int validity = 0; - public int type = 1; + public int type = TransactionViewModel.PREFILLED_SLOT; public long arrivalTime = 0; //public boolean confirmed = false; @@ -48,6 +48,7 @@ public void read(byte[] bytes) { if(bytes != null) { this.bytes = new byte[SIZE]; System.arraycopy(bytes, 0, this.bytes, 0, SIZE); + this.type = TransactionViewModel.FILLED_SLOT; } } diff --git a/src/main/java/com/iota/iri/network/Node.java b/src/main/java/com/iota/iri/network/Node.java index 331e7342c0..5b4d5a8587 100644 --- a/src/main/java/com/iota/iri/network/Node.java +++ b/src/main/java/com/iota/iri/network/Node.java @@ -82,9 +82,10 @@ public class Node { - private final LRUHashCache recentSeenHashes = new LRUHashCache(5000); - private final LRUByteCache recentSeenBytes = new LRUByteCache(15000); + private final LRUCache recentSeenHashes = new LRUCache<>(5000); + private final LRUCache recentSeenBytes = new LRUCache<>(15000); + private boolean debug; private static AtomicLong recentSeenBytesMissCount = new AtomicLong(0L); private static AtomicLong recentSeenBytesHitCount = new AtomicLong(0L); @@ -120,6 +121,7 @@ public void init() throws Exception { P_REPLY_RANDOM_TIP = configuration.doubling(Configuration.DefaultConfSettings.P_REPLY_RANDOM_TIP.name()); P_PROPAGATE_REQUEST = configuration.doubling(Configuration.DefaultConfSettings.P_PROPAGATE_REQUEST.name()); sendLimit = (long) ( (configuration.doubling(Configuration.DefaultConfSettings.SEND_LIMIT.name()) * 1000000) / (TRANSACTION_PACKET_SIZE * 8) ); + debug = configuration.booling(Configuration.DefaultConfSettings.DEBUG); Arrays.stream(configuration.string(Configuration.DefaultConfSettings.NEIGHBORS).split(" ")).distinct() .filter(s -> !s.isEmpty()).map(Node::uri).map(Optional::get).peek(u -> { @@ -152,7 +154,7 @@ public DatagramSocket getUdpSocket() { } private final Map neighborIpCache = new HashMap<>(); - + private Runnable spawnNeighborDNSRefresherThread() { return () -> { @@ -212,32 +214,33 @@ private Runnable spawnNeighborDNSRefresherThread() { } private Optional checkIp(final String dnsName) { - + if (StringUtils.isEmpty(dnsName)) { return Optional.empty(); } - + InetAddress inetAddress; try { inetAddress = java.net.InetAddress.getByName(dnsName); } catch (UnknownHostException e) { return Optional.empty(); } - + final String hostAddress = inetAddress.getHostAddress(); - + if (StringUtils.equals(dnsName, hostAddress)) { // not a DNS... return Optional.empty(); } - + return Optional.of(hostAddress); } public void preProcessReceivedData(byte[] receivedData, SocketAddress senderAddress, String uriScheme) { TransactionViewModel receivedTransactionViewModel = null; Hash receivedTransactionHash = null; - boolean addressMatch = false; + boolean cached = false; + for (final Neighbor neighbor : getNeighbors()) { addressMatch = neighbor.matches(senderAddress); if (addressMatch) { @@ -251,42 +254,29 @@ public void preProcessReceivedData(byte[] receivedData, SocketAddress senderAddr //Transaction bytes - //final int byteHash = ByteBuffer.wrap(receivedData, 0, TransactionViewModel.SIZE).hashCode(); MessageDigest digest = MessageDigest.getInstance("SHA-256"); digest.update(receivedData, 0, TransactionViewModel.SIZE); ByteBuffer byteHash = ByteBuffer.wrap(digest.digest()); - + //check if cached synchronized (recentSeenBytes) { - receivedTransactionHash = recentSeenBytes.get(byteHash); + cached = (receivedTransactionHash = recentSeenBytes.get(byteHash)) != null; } - if (receivedTransactionHash == null) { + if (!cached) { //if not, then validate receivedTransactionViewModel = TransactionValidator.validate(receivedData, transactionValidator.getMinWeightMagnitude()); receivedTransactionHash = receivedTransactionViewModel.getHash(); - //if valid - add to receive queue (receivedTransactionViewModel, neighbor) - addReceivedDataToReceiveQueue(receivedTransactionViewModel, neighbor); - synchronized (recentSeenBytes) { - recentSeenBytes.set(byteHash, receivedTransactionHash); + recentSeenBytes.put(byteHash, receivedTransactionHash); } - recentSeenBytesMissCount.getAndIncrement(); + //if valid - add to receive queue (receivedTransactionViewModel, neighbor) + addReceivedDataToReceiveQueue(receivedTransactionViewModel, neighbor); } - else { - recentSeenBytesHitCount.getAndIncrement(); - } - if (((recentSeenBytesMissCount.get() + recentSeenBytesHitCount.get()) % 50000L == 0)) { - log.info("RecentSeenBytes cache hit/miss ratio: "+recentSeenBytesHitCount.get()+"/"+recentSeenBytesMissCount.get()); - messageQ.publish("hmr %d/%d",recentSeenBytesHitCount.get(), recentSeenBytesMissCount.get()); - recentSeenBytesMissCount.set(0L); - recentSeenBytesHitCount.set(0L); - } - } catch (NoSuchAlgorithmException e) { log.error("MessageDigest: "+e); } catch (final RuntimeException e) { @@ -307,6 +297,25 @@ public void preProcessReceivedData(byte[] receivedData, SocketAddress senderAddr addReceivedDataToReplyQueue(requestedHash, neighbor); + //recentSeenBytes statistics + + if (debug) { + long hitCount, missCount; + if(cached) { + hitCount = recentSeenBytesHitCount.incrementAndGet(); + missCount = recentSeenBytesMissCount.get(); + } else { + hitCount = recentSeenBytesHitCount.get(); + missCount = recentSeenBytesMissCount.getAndIncrement(); + } + if (((hitCount + missCount) % 50000L == 0)) { + log.info("RecentSeenBytes cache hit/miss ratio: " + hitCount + "/" + missCount); + messageQ.publish("hmr %d/%d", hitCount, missCount); + recentSeenBytesMissCount.set(0L); + recentSeenBytesHitCount.set(0L); + } + } + break; } } @@ -334,7 +343,7 @@ public void preProcessReceivedData(byte[] receivedData, SocketAddress senderAddr log.error("Invalid URI string: " + uriString); } } - else { + else { if ( rejectedAddresses.size() > 20 ) { // Avoid ever growing list in case of an attack. rejectedAddresses.clear(); @@ -342,7 +351,7 @@ public void preProcessReceivedData(byte[] receivedData, SocketAddress senderAddr else if ( rejectedAddresses.add(uriString) ) { messageQ.publish("rntn %s %s", uriString, String.valueOf(maxPeersAllowed)); log.info("Refused non-tethered neighbor: " + uriString + - " (max-peers = "+ String.valueOf(maxPeersAllowed) + ")"); + " (max-peers = "+ String.valueOf(maxPeersAllowed) + ")"); } } } @@ -385,18 +394,14 @@ public void processReceivedData(TransactionViewModel receivedTransactionViewMode //store new transaction try { - //first check if Hash seen recently + //first check if Hash seen recently & update seen. synchronized (recentSeenHashes) { - cached = recentSeenHashes.get(receivedTransactionViewModel.getHash()); + cached = (recentSeenHashes.put(receivedTransactionViewModel.getHash(), true)) != null; + } - if (cached) { - stored = false; - } else { - //if not, store tx. & update recentSeenHashes + if (!cached) { + //if not, store tx. stored = receivedTransactionViewModel.store(tangle); - synchronized (recentSeenHashes) { - recentSeenHashes.set(receivedTransactionViewModel.getHash(), true); - } } } catch (Exception e) { log.error("Error accessing persistence store.", e); @@ -410,7 +415,6 @@ public void processReceivedData(TransactionViewModel receivedTransactionViewMode transactionValidator.updateStatus(receivedTransactionViewModel); receivedTransactionViewModel.updateSender(neighbor.getAddress().toString()); receivedTransactionViewModel.update(tangle, "arrivalTime|sender"); - } catch (Exception e) { log.error("Error updating transactions.", e); } @@ -546,7 +550,7 @@ private Runnable spawnTipRequesterThread() { System.arraycopy(transactionViewModel.getBytes(), 0, tipRequestingPacket.getData(), 0, TransactionViewModel.SIZE); System.arraycopy(transactionViewModel.getHash().bytes(), 0, tipRequestingPacket.getData(), TransactionViewModel.SIZE, TransactionRequester.REQUEST_HASH_SIZE); - //Hash.SIZE_IN_BYTES); + //Hash.SIZE_IN_BYTES); neighbors.forEach(n -> n.send(tipRequestingPacket)); @@ -697,7 +701,7 @@ public Neighbor newNeighbor(final URI uri, boolean isConfigured) { } return neighbor; } - + public static Optional uri(final String uri) { try { return Optional.of(new URI(uri)); @@ -731,67 +735,35 @@ public int getReplyQueueSize() { return replyQueue.size(); } - public class LRUHashCache { - - private int capacity; - private LinkedHashMap map; - - public LRUHashCache(int capacity) { - this.capacity = capacity; - this.map = new LinkedHashMap<>(); - } - - public Boolean get(Hash key) { - Boolean value = this.map.get(key); - if (value == null) { - value = false; - } else { - this.set(key, value); - } - return value; - } - - public void set(Hash key, Boolean value) { - if (this.map.containsKey(key)) { - this.map.remove(key); - } else if (this.map.size() == this.capacity) { - Iterator it = this.map.keySet().iterator(); - it.next(); - it.remove(); - } - map.put(key, value); - } - } - - public class LRUByteCache { + public class LRUCache { private int capacity; - private LinkedHashMap map; + private LinkedHashMap map; - public LRUByteCache(int capacity) { + public LRUCache(int capacity) { this.capacity = capacity; this.map = new LinkedHashMap<>(); } - public Hash get(ByteBuffer key) { - Hash value = this.map.get(key); + public V get(K key) { + V value = this.map.get(key); if (value == null) { value = null; } else { - this.set(key, value); + this.put(key, value); } return value; } - public void set(ByteBuffer key, Hash value) { + public V put(K key, V value) { if (this.map.containsKey(key)) { this.map.remove(key); } else if (this.map.size() == this.capacity) { - Iterator it = this.map.keySet().iterator(); + Iterator it = this.map.keySet().iterator(); it.next(); it.remove(); } - map.put(key, value); + return map.put(key, value); } } diff --git a/src/main/java/com/iota/iri/service/API.java b/src/main/java/com/iota/iri/service/API.java index 85f244353a..e2f84ce929 100644 --- a/src/main/java/com/iota/iri/service/API.java +++ b/src/main/java/com/iota/iri/service/API.java @@ -76,6 +76,8 @@ import io.undertow.util.MimeMappings; import io.undertow.util.StatusCodes; +import javax.xml.bind.ValidationException; + @SuppressWarnings("unchecked") public class API { @@ -99,7 +101,11 @@ public class API { private final int minRandomWalks; private final int maxRandomWalks; private final int maxFindTxs; + private final int maxRequestList; private final int maxGetTrytes; + private final int maxBodyLength; + private final static String overMaxErrorMessage = "Could not complete request"; + private final static String invalidParams = "Invalid parameters"; private final static char ZERO_LENGTH_ALLOWED = 'Y'; private final static char ZERO_LENGTH_NOT_ALLOWED = 'N'; @@ -111,15 +117,16 @@ public API(Iota instance, IXI ixi) { minRandomWalks = instance.configuration.integer(DefaultConfSettings.MIN_RANDOM_WALKS); maxRandomWalks = instance.configuration.integer(DefaultConfSettings.MAX_RANDOM_WALKS); maxFindTxs = instance.configuration.integer(DefaultConfSettings.MAX_FIND_TRANSACTIONS); + maxRequestList = instance.configuration.integer(DefaultConfSettings.MAX_REQUESTS_LIST); maxGetTrytes = instance.configuration.integer(DefaultConfSettings.MAX_GET_TRYTES); - + maxBodyLength = instance.configuration.integer(DefaultConfSettings.MAX_BODY_LENGTH); } public void init() throws IOException { final int apiPort = instance.configuration.integer(DefaultConfSettings.PORT); final String apiHost = instance.configuration.string(DefaultConfSettings.API_HOST); - log.debug("Binding JSON-REST API Undertown server on {}:{}", apiHost, apiPort); + log.debug("Binding JSON-REST API Undertow server on {}:{}", apiHost, apiPort); server = Undertow.builder().addHttpListener(apiPort, apiHost) .setHandler(path().addPrefixPath("/", addSecurity(new HttpHandler() { @@ -156,10 +163,13 @@ private void processRequest(final HttpServerExchange exchange) throws IOExceptio final long beginningTime = System.currentTimeMillis(); final String body = IOUtils.toString(cis, StandardCharsets.UTF_8); final AbstractResponse response; - if (exchange.getRequestHeaders().contains("X-IOTA-API-Version")) { - response = process(body, exchange.getSourceAddress()); - } else { + + if (!exchange.getRequestHeaders().contains("X-IOTA-API-Version")) { response = ErrorResponse.create("Invalid API Version"); + } else if (body.length() > maxBodyLength) { + response = ErrorResponse.create("Request too long"); + } else { + response = process(body, exchange.getSourceAddress()); } sendResponse(exchange, response, beginningTime); } @@ -188,102 +198,41 @@ private AbstractResponse process(final String requestString, InetSocketAddress s switch (command) { case "addNeighbors": { - if (!request.containsKey("uris")) { - return ErrorResponse.create("Invalid params"); - } - final List uris = (List) request.get("uris"); + List uris = getParameterAsList(request,"uris",0); log.debug("Invoking 'addNeighbors' with {}", uris); - return addNeighborsStatement(uris); } case "attachToTangle": { - if (!request.containsKey("trunkTransaction") || - !request.containsKey("branchTransaction") || - !request.containsKey("minWeightMagnitude") || - !request.containsKey("trytes")) { - return ErrorResponse.create("Invalid params"); - } - if (!validTrytes((String)request.get("trunkTransaction"), HASH_SIZE, ZERO_LENGTH_NOT_ALLOWED)) { - return ErrorResponse.create("Invalid trunkTransaction hash"); - } - if (!validTrytes((String)request.get("branchTransaction"), HASH_SIZE, ZERO_LENGTH_NOT_ALLOWED)) { - return ErrorResponse.create("Invalid branchTransaction hash"); - } - final Hash trunkTransaction = new Hash((String) request.get("trunkTransaction")); - final Hash branchTransaction = new Hash((String) request.get("branchTransaction")); - final int minWeightMagnitude; - try { - minWeightMagnitude = ((Double) request.get("minWeightMagnitude")).intValue(); - } catch (ClassCastException e) { - return ErrorResponse.create("Invalid minWeightMagnitude input"); - } - final List trytes = (List) request.get("trytes"); - for (final String tryt : trytes) { - if (!validTrytes(tryt, TRYTES_SIZE, ZERO_LENGTH_NOT_ALLOWED)) { - return ErrorResponse.create("Invalid trytes input"); - } - } + final Hash trunkTransaction = new Hash(getParameterAsStringAndValidate(request,"trunkTransaction", HASH_SIZE)); + final Hash branchTransaction = new Hash(getParameterAsStringAndValidate(request,"branchTransaction", HASH_SIZE)); + final int minWeightMagnitude = getParameterAsInt(request,"minWeightMagnitude"); + + final List trytes = getParameterAsList(request,"trytes", TRYTES_SIZE); + List elements = attachToTangleStatement(trunkTransaction, branchTransaction, minWeightMagnitude, trytes); return AttachToTangleResponse.create(elements); } case "broadcastTransactions": { - if (!request.containsKey("trytes")) { - return ErrorResponse.create("Invalid params"); - } - final List trytes = (List) request.get("trytes"); - for (final String tryt : trytes) { - if (!validTrytes(tryt, TRYTES_SIZE, ZERO_LENGTH_NOT_ALLOWED)) { - return ErrorResponse.create("Invalid trytes input"); - } - } - broadcastTransactionStatement(trytes); + broadcastTransactionStatement(getParameterAsList(request,"trytes", TRYTES_SIZE)); return AbstractResponse.createEmptyResponse(); } case "findTransactions": { - if (!request.containsKey("bundles") && - !request.containsKey("addresses") && - !request.containsKey("tags") && - !request.containsKey("approvees")) { - return ErrorResponse.create("Invalid params"); - } return findTransactionStatement(request); } case "getBalances": { - if (!request.containsKey("addresses") || !request.containsKey("threshold")) { - return ErrorResponse.create("Invalid params"); - } - final List addresses = (List) request.get("addresses"); - for (final String address : addresses) { - if (!validTrytes(address, HASH_SIZE, ZERO_LENGTH_NOT_ALLOWED)) { - return ErrorResponse.create("Invalid addresses input"); - } - } - final int threshold = ((Double) request.get("threshold")).intValue(); + final List addresses = getParameterAsList(request,"addresses", HASH_SIZE); + final int threshold = getParameterAsInt(request, "threshold"); return getBalancesStatement(addresses, threshold); } case "getInclusionStates": { - if (!request.containsKey("transactions") || !request.containsKey("tips")) { - return ErrorResponse.create("Invalid params"); - } - final List trans = (List) request.get("transactions"); - final List tps = (List) request.get("tips"); - - for (final String tx : trans) { - if (!validTrytes(tx, HASH_SIZE, ZERO_LENGTH_NOT_ALLOWED)) { - return ErrorResponse.create("Invalid transactions input"); - } - } - for (final String ti : tps) { - if (!validTrytes(ti, HASH_SIZE, ZERO_LENGTH_NOT_ALLOWED)) { - return ErrorResponse.create("Invalid tips input"); - } - } - if (invalidSubtangleStatus()) { return ErrorResponse .create("This operations cannot be executed: The subtangle has not been updated yet."); } - return getNewInclusionStateStatement(trans, tps); + final List transactions = getParameterAsList(request,"transactions", HASH_SIZE); + final List tips = getParameterAsList(request,"tips", HASH_SIZE); + + return getNewInclusionStateStatement(transactions, tips); } case "getNeighbors": { return getNeighborsStatement(); @@ -302,23 +251,18 @@ private AbstractResponse process(final String requestString, InetSocketAddress s return getTipsStatement(); } case "getTransactionsToApprove": { - final int depth; - try { - depth = ((Double) request.get("depth")).intValue(); - } catch (ClassCastException e) { - return ErrorResponse.create("Invalid depth input"); - } - final Object referenceObj = request.get("reference"); - final String reference = referenceObj == null? null: (String) referenceObj; if (invalidSubtangleStatus()) { return ErrorResponse .create("This operations cannot be executed: The subtangle has not been updated yet."); } - final Object numWalksObj = request.get("numWalks"); - int numWalks = numWalksObj == null? 1 : ((Double) numWalksObj).intValue(); + + final int depth = getParameterAsInt(request, "depth"); + final String reference = request.containsKey("reference") ? getParameterAsStringAndValidate(request,"reference", HASH_SIZE) : null; + int numWalks = request.containsKey("numWalks") ? getParameterAsInt(request,"numWalks") : 1; if(numWalks < minRandomWalks) { numWalks = minRandomWalks; } + final Hash[] tips = getTransactionToApproveStatement(depth, reference, numWalks); if(tips == null) { return ErrorResponse.create("The subtangle is not solid"); @@ -326,18 +270,7 @@ private AbstractResponse process(final String requestString, InetSocketAddress s return GetTransactionsToApproveResponse.create(tips[0], tips[1]); } case "getTrytes": { - if (!request.containsKey("hashes")) { - return ErrorResponse.create("Invalid params"); - } - final List hashes = (List) request.get("hashes"); - if (hashes == null) { - return ErrorResponse.create("Wrong arguments"); - } - for (final String hash : hashes) { - if (!validTrytes(hash, HASH_SIZE, ZERO_LENGTH_NOT_ALLOWED)) { - return ErrorResponse.create("Invalid hash input"); - } - } + final List hashes = getParameterAsList(request,"hashes", HASH_SIZE); return getTrytesStatement(hashes); } @@ -346,23 +279,16 @@ private AbstractResponse process(final String requestString, InetSocketAddress s return AbstractResponse.createEmptyResponse(); } case "removeNeighbors": { - if (!request.containsKey("uris")) { - return ErrorResponse.create("Invalid params"); - } - final List uris = (List) request.get("uris"); + List uris = getParameterAsList(request,"uris",0); log.debug("Invoking 'removeNeighbors' with {}", uris); return removeNeighborsStatement(uris); } case "storeTransactions": { - if (!request.containsKey("trytes")) { - return ErrorResponse.create("Invalid params"); - } - List trytes = (List) request.get("trytes"); - log.debug("Invoking 'storeTransactions' with {}", trytes); - if(storeTransactionStatement(trytes)) { - return AbstractResponse.createEmptyResponse(); - } else { + try { + storeTransactionStatement(getParameterAsList(request,"trytes", TRYTES_SIZE)); + } catch (RuntimeException e) { + //transaction not valid return ErrorResponse.create("Invalid trytes input"); } } @@ -383,22 +309,73 @@ private AbstractResponse process(final String requestString, InetSocketAddress s } } + } catch (final ValidationException e) { + log.info("API Validation failed: " + e.getLocalizedMessage()); + return ErrorResponse.create(e.getLocalizedMessage()); } catch (final Exception e) { log.error("API Exception: ", e); return ExceptionResponse.create(e.getLocalizedMessage()); } } + private int getParameterAsInt(Map request, String paramName) throws ValidationException { + validateParamExists(request, paramName); + final int result; + try { + result = ((Double) request.get(paramName)).intValue(); + } catch (ClassCastException e) { + throw new ValidationException("Invalid " + paramName + " input"); + } + return result; + } + + private String getParameterAsStringAndValidate(Map request, String paramName, int size) throws ValidationException { + validateParamExists(request, paramName); + String result = (String) request.get(paramName); + validateTrytes(paramName, size, result); + return result; + } + + private void validateTrytes(String paramName, int size, String result) throws ValidationException { + if (!validTrytes(result,size,ZERO_LENGTH_NOT_ALLOWED)) { + throw new ValidationException("Invalid " + paramName + " input"); + } + } + + private void validateParamExists(Map request, String paramName) throws ValidationException { + if (!request.containsKey(paramName)) { + throw new ValidationException(invalidParams); + } + } + + private List getParameterAsList(Map request, String paramName, int size) throws ValidationException { + validateParamExists(request, paramName); + final List paramList = (List) request.get(paramName); + if (paramList.size() > maxRequestList) { + throw new ValidationException(overMaxErrorMessage); + } + + if (size > 0) { + //validate + for (final String param : paramList) { + validateTrytes(paramName, size, param); + } + } + + return paramList; + + } + public boolean invalidSubtangleStatus() { return (instance.milestone.latestSolidSubtangleMilestoneIndex == Milestone.MILESTONE_START_INDEX); } private AbstractResponse removeNeighborsStatement(List uris) throws URISyntaxException { final AtomicInteger numberOfRemovedNeighbors = new AtomicInteger(0); - + for (final String uriString : uris) { final URI uri = new URI(uriString); - + if ("udp".equals(uri.getScheme()) || "tcp".equals(uri.getScheme())) { log.info("Removing neighbor: "+uriString); if (instance.node.removeNeighbor(uri,true)) { @@ -421,7 +398,7 @@ private synchronized AbstractResponse getTrytesStatement(List hashes) th } } if (elements.size() > maxGetTrytes){ - return ErrorResponse.create("Could not complete request"); + return ErrorResponse.create(overMaxErrorMessage); } return GetTrytesResponse.create(elements); } @@ -433,7 +410,7 @@ public static int getCounter_getTxToApprove() { public static void incCounter_getTxToApprove() { counter_getTxToApprove++; } - + private static long ellapsedTime_getTxToApprove = 0L; public static long getEllapsedTime_getTxToApprove() { return ellapsedTime_getTxToApprove; @@ -476,12 +453,8 @@ private synchronized AbstractResponse getTipsStatement() throws Exception { return GetTipsResponse.create(instance.tipsViewModel.getTips().stream().map(Hash::toString).collect(Collectors.toList())); } - public boolean storeTransactionStatement(final List trys) throws Exception { + public void storeTransactionStatement(final List trys) throws Exception { for (final String trytes : trys) { - - if (!validTrytes(trytes, TRYTES_SIZE, ZERO_LENGTH_NOT_ALLOWED)) { - return false; - } final TransactionViewModel transactionViewModel = instance.transactionValidator.validate(Converter.trits(trytes), instance.transactionValidator.getMinWeightMagnitude()); if(transactionViewModel.store(instance.tangle)) { @@ -491,7 +464,6 @@ public boolean storeTransactionStatement(final List trys) throws Excepti transactionViewModel.update(instance.tangle, "sender"); } } - return true; } private AbstractResponse getNeighborsStatement() { @@ -594,71 +566,70 @@ private boolean exhaustiveSearchWithinIndex(Queue nonAnalyzedTransactions, } private synchronized AbstractResponse findTransactionStatement(final Map request) throws Exception { - final Set bundlesTransactions = new HashSet<>(); + final Set foundTransactions = new HashSet<>(); + boolean containsKey = false; + final Set bundlesTransactions = new HashSet<>(); if (request.containsKey("bundles")) { - for (final String bundle : (List) request.get("bundles")) { - if (!validTrytes(bundle, HASH_SIZE, ZERO_LENGTH_NOT_ALLOWED)) { - return ErrorResponse.create("Invalid bundle hash"); - } + final HashSet bundles = getParameterAsSet(request,"bundles",HASH_SIZE); + for (final String bundle : bundles) { bundlesTransactions.addAll(BundleViewModel.load(instance.tangle, new Hash(bundle)).getHashes()); } + foundTransactions.addAll(bundlesTransactions); + containsKey = true; } final Set addressesTransactions = new HashSet<>(); if (request.containsKey("addresses")) { - final List addresses = (List) request.get("addresses"); - log.debug("Searching: {}", addresses.stream().reduce((a, b) -> a += ',' + b)); - + final HashSet addresses = getParameterAsSet(request,"addresses",HASH_SIZE); for (final String address : addresses) { - if (!validTrytes(address, HASH_SIZE, ZERO_LENGTH_NOT_ALLOWED)) { - return ErrorResponse.create("Invalid address input"); - } addressesTransactions.addAll(AddressViewModel.load(instance.tangle, new Hash(address)).getHashes()); } + foundTransactions.addAll(addressesTransactions); + containsKey = true; } final Set tagsTransactions = new HashSet<>(); if (request.containsKey("tags")) { - for (String tag : (List) request.get("tags")) { - if (!validTrytes(tag,tag.length(), ZERO_LENGTH_NOT_ALLOWED)) { - return ErrorResponse.create("Invalid tag input"); - } - while (tag.length() < Curl.HASH_LENGTH / Converter.NUMBER_OF_TRITS_IN_A_TRYTE) { - tag += Converter.TRYTE_ALPHABET.charAt(0); - } + final HashSet tags = getParameterAsSet(request,"tags",0); + for (String tag : tags) { + tag = padTag(tag); tagsTransactions.addAll(TagViewModel.load(instance.tangle, new Hash(tag)).getHashes()); } + foundTransactions.addAll(tagsTransactions); + containsKey = true; } final Set approveeTransactions = new HashSet<>(); if (request.containsKey("approvees")) { - for (final String approvee : (List) request.get("approvees")) { - if (!validTrytes(approvee,HASH_SIZE, ZERO_LENGTH_NOT_ALLOWED)) { - return ErrorResponse.create("Invalid approvees hash"); - } + final HashSet approvees = getParameterAsSet(request,"approvees",HASH_SIZE); + for (final String approvee : approvees) { approveeTransactions.addAll(TransactionViewModel.fromHash(instance.tangle, new Hash(approvee)).getApprovers(instance.tangle).getHashes()); } + foundTransactions.addAll(approveeTransactions); + containsKey = true; } - // need refactoring - final Set foundTransactions = bundlesTransactions.isEmpty() ? (addressesTransactions.isEmpty() - ? (tagsTransactions.isEmpty() - ? (approveeTransactions.isEmpty() ? new HashSet<>() : approveeTransactions) : tagsTransactions) - : addressesTransactions) : bundlesTransactions; + if (!containsKey) { + throw new ValidationException(invalidParams); + } - if (!addressesTransactions.isEmpty()) { + //Using multiple of these input fields returns the intersection of the values. + if (request.containsKey("bundles")) { + foundTransactions.retainAll(bundlesTransactions); + } + if (request.containsKey("addresses")) { foundTransactions.retainAll(addressesTransactions); } - if (!tagsTransactions.isEmpty()) { + if (request.containsKey("tags")) { foundTransactions.retainAll(tagsTransactions); } - if (!approveeTransactions.isEmpty()) { + if (request.containsKey("approvees")) { foundTransactions.retainAll(approveeTransactions); } if (foundTransactions.size() > maxFindTxs){ - return ErrorResponse.create("Could not complete request"); + return ErrorResponse.create(overMaxErrorMessage); } final List elements = foundTransactions.stream() @@ -668,6 +639,25 @@ private synchronized AbstractResponse findTransactionStatement(final Map getParameterAsSet(Map request, String paramName, int size) throws ValidationException { + + HashSet result = getParameterAsList(request,paramName,size).stream().collect(Collectors.toCollection(HashSet::new)); + if (result.contains(Hash.NULL_HASH.toString())) { + throw new ValidationException("Invalid " + paramName + " input"); + } + return result; + } + public void broadcastTransactionStatement(final List trytes2) { for (final String tryte : trytes2) { //validate PoW - throws exception if invalid @@ -702,32 +692,31 @@ private AbstractResponse getBalancesStatement(final List addrss, final i final int milestoneIndex = instance.milestone.latestSolidSubtangleMilestoneIndex; - Set analyzedTips = new HashSet<>(); + Set analyzedTips = new HashSet<>(); - final Queue nonAnalyzedTransactions = new LinkedList<>(Collections.singleton(milestone)); - //Collections.singleton(StorageTransactions.instance().transactionPointer(milestone.value()))); - Hash hash; - while ((hash = nonAnalyzedTransactions.poll()) != null) { + final Queue nonAnalyzedTransactions = new LinkedList<>(Collections.singleton(milestone)); + Hash hash; + while ((hash = nonAnalyzedTransactions.poll()) != null) { - if (analyzedTips.add(hash)) { + if (analyzedTips.add(hash)) { - final TransactionViewModel transactionViewModel = TransactionViewModel.fromHash(instance.tangle, hash); + final TransactionViewModel transactionViewModel = TransactionViewModel.fromHash(instance.tangle, hash); - if(transactionViewModel.snapshotIndex() == 0 || transactionViewModel.snapshotIndex() > index) { - if (transactionViewModel.value() != 0) { + if(transactionViewModel.snapshotIndex() == 0 || transactionViewModel.snapshotIndex() > index) { + if (transactionViewModel.value() != 0) { - final Hash address = transactionViewModel.getAddressHash(); - final Long balance = balances.get(address); - if (balance != null) { + final Hash address = transactionViewModel.getAddressHash(); + final Long balance = balances.get(address); + if (balance != null) { - balances.put(address, balance + transactionViewModel.value()); - } + balances.put(address, balance + transactionViewModel.value()); } - nonAnalyzedTransactions.offer(transactionViewModel.getTrunkTransactionHash()); - nonAnalyzedTransactions.offer(transactionViewModel.getBranchTransactionHash()); } + nonAnalyzedTransactions.offer(transactionViewModel.getTrunkTransactionHash()); + nonAnalyzedTransactions.offer(transactionViewModel.getBranchTransactionHash()); } } + } final List elements = addresses.stream().map(address -> balances.get(address).toString()) .collect(Collectors.toCollection(LinkedList::new)); @@ -820,7 +809,7 @@ private AbstractResponse addNeighborsStatement(final List uris) throws U int numberOfAddedNeighbors = 0; for (final String uriString : uris) { final URI uri = new URI(uriString); - + if ("udp".equals(uri.getScheme()) || "tcp".equals(uri.getScheme())) { log.info("Adding neighbor: "+uriString); // 3rd parameter true if tcp, 4th parameter true (configured tethering) @@ -884,11 +873,11 @@ private void sendResponse(final HttpServerExchange exchange, final AbstractRespo sinkChannel.resumeWrites(); } - private boolean validTrytes(String trytes, int minimalLength, char zeroAllowed) { + private boolean validTrytes(String trytes, int length, char zeroAllowed) { if (trytes.length() == 0 && zeroAllowed == ZERO_LENGTH_ALLOWED) { return true; } - if (trytes.length() < minimalLength) { + if (trytes.length() != length) { return false; } Matcher matcher = trytesPattern.matcher(trytes); @@ -900,7 +889,7 @@ private static void setupResponseHeaders(final HttpServerExchange exchange) { headerMap.add(new HttpString("Access-Control-Allow-Origin"),"*"); headerMap.add(new HttpString("Keep-Alive"), "timeout=500, max=100"); } - + private HttpHandler addSecurity(final HttpHandler toWrap) { String credentials = instance.configuration.string(DefaultConfSettings.REMOTE_AUTH); if(credentials == null || credentials.isEmpty()) return toWrap; diff --git a/src/main/java/com/iota/iri/storage/rocksDB/RocksDBPersistenceProvider.java b/src/main/java/com/iota/iri/storage/rocksDB/RocksDBPersistenceProvider.java index ec2c06b922..795cb0c874 100644 --- a/src/main/java/com/iota/iri/storage/rocksDB/RocksDBPersistenceProvider.java +++ b/src/main/java/com/iota/iri/storage/rocksDB/RocksDBPersistenceProvider.java @@ -197,11 +197,8 @@ public Persistable get(Class model, Indexable index) throws Exception { @Override public boolean mayExist(Class model, Indexable index) throws Exception { - /* for version 5.4.5 ColumnFamilyHandle handle = classTreeMap.get().get(model); return db.keyMayExist(handle, index.bytes(), new StringBuilder()); - */ - return db.keyMayExist(classTreeMap.get().get(model), index.bytes(), new StringBuffer()); } @Override diff --git a/src/test/java/com/iota/iri/APIIntegrationTests.java b/src/test/java/com/iota/iri/APIIntegrationTests.java index 8a6afdc238..3d1777eb89 100644 --- a/src/test/java/com/iota/iri/APIIntegrationTests.java +++ b/src/test/java/com/iota/iri/APIIntegrationTests.java @@ -15,7 +15,7 @@ public class APIIntegrationTests { private static final Gson gson = new GsonBuilder().create(); static { - RestAssured.port = 14700; + RestAssured.port = 14265; } /** @@ -31,7 +31,7 @@ public void shouldTestGetNodeInfo() { request.put("command", "getNodeInfo"); given(). - contentType("application/json"). + contentType("application/json").header("X-IOTA-API-Version",1). body(gson.toJson(request)). when(). post("/"). @@ -70,7 +70,7 @@ public void shouldTestGetNeighbors() { request.put("command", "getNeighbors"); given(). - contentType("application/json"). + contentType("application/json").header("X-IOTA-API-Version",1). body(gson.toJson(request)). when(). post("/"). @@ -96,7 +96,7 @@ public void shouldTestAddNeighbors() { request.put("command", "addNeighbors"); request.put("uris", new String [] {"udp://8.8.8.8:14265", "udp://8.8.8.5:14265"} ); given(). - contentType("application/json"). + contentType("application/json").header("X-IOTA-API-Version",1). body(gson.toJson(request)). when(). post("/"). @@ -119,7 +119,7 @@ public void shouldTestRemoveNeighbors() { request.put("command", "removeNeighbors"); request.put("uris", new String [] {"udp://8.8.8.8:14265", "udp://8.8.8.5:14265"} ); given(). - contentType("application/json"). + contentType("application/json").header("X-IOTA-API-Version",1). body(gson.toJson(request)). when(). post("/"). @@ -142,7 +142,7 @@ public void shouldTestGetTips() { request.put("command", "getTips"); given(). - contentType("application/json"). + contentType("application/json").header("X-IOTA-API-Version",1). body(gson.toJson(request)). when(). post("/"). @@ -165,7 +165,7 @@ public void shouldTestFindTransactions() { request.put("command", "findTransactions"); request.put("addresses", new String [] {"RVORZ9SIIP9RCYMREUIXXVPQIPHVCNPQ9HZWYKFWYWZRE9JQKG9REPKIASHUUECPSQO9JT9XNMVKWYGVAZETAIRPTM"}); given(). - contentType("application/json"). + contentType("application/json").header("X-IOTA-API-Version",1). body(gson.toJson(request)). when(). post("/"). @@ -188,7 +188,7 @@ public void shouldTestGetTrytes() { request.put("command", "getTrytes"); request.put("hashes", new String [] {"OAATQS9VQLSXCLDJVJJVYUGONXAXOFMJOZNSYWRZSWECMXAQQURHQBJNLD9IOFEPGZEPEMPXCIVRX9999"}); given(). - contentType("application/json"). + contentType("application/json").header("X-IOTA-API-Version",1). body(gson.toJson(request)). when(). post("/"). @@ -212,7 +212,7 @@ public void shouldTestGetInclusionStates() { request.put("transactions", new String [] {"999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"}); request.put("tips", new String [] {"999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"}); given(). - contentType("application/json"). + contentType("application/json").header("X-IOTA-API-Version",1). body(gson.toJson(request)). when(). post("/"). @@ -237,7 +237,7 @@ public void shouldTestGetBalances() { request.put("threshold", 100); given(). - contentType("application/json"). + contentType("application/json").header("X-IOTA-API-Version",1). body(gson.toJson(request)). when(). post("/"). @@ -261,7 +261,7 @@ public void shouldTestGetTransactionsToApprove() { request.put("depth", 27); given(). - contentType("application/json"). + contentType("application/json").header("X-IOTA-API-Version",1). body(gson.toJson(request)). when(). post("/"). @@ -286,7 +286,7 @@ public void shouldTestBroadcastTransactions() { request.put("trytes", new String[] {"BYSWEAUTWXHXZ9YBZISEK9LUHWGMHXCGEVNZHRLUWQFCUSDXZHOFHWHL9MQPVJXXZLIXPXPXF9KYEREFSKCPKYIIKPZVLHUTDFQKKVVBBN9ATTLPCNPJDWDEVIYYLGPZGCWXOBDXMLJC9VO9QXTTBLAXTTBFUAROYEGQIVB9MJWJKXJMCUPTWAUGFZBTZCSJVRBGMYXTVBDDS9MYUJCPZ9YDWWQNIPUAIJXXSNLKUBSCOIJPCLEFPOXFJREXQCUVUMKSDOVQGGHRNILCO9GNCLWFM9APMNMWYASHXQAYBEXF9QRIHIBHYEJOYHRQJAOKAQ9AJJFQ9WEIWIJOTZATIBOXQLBMIJU9PCGBLVDDVFP9CFFSXTDUXMEGOOFXWRTLFGV9XXMYWEMGQEEEDBTIJ9OJOXFAPFQXCDAXOUDMLVYRMRLUDBETOLRJQAEDDLNVIRQJUBZBO9CCFDHIX9MSQCWYAXJVWHCUPTRSXJDESISQPRKZAFKFRULCGVRSBLVFOPEYLEE99JD9SEBALQINPDAZHFAB9RNBH9AZWIJOTLBZVIEJIAYGMC9AZGNFWGRSWAXTYSXVROVNKCOQQIWGPNQZKHUNODGYADPYLZZZUQRTJRTODOUKAOITNOMWNGHJBBA99QUMBHRENGBHTH9KHUAOXBVIVDVYYZMSEYSJWIOGGXZVRGN999EEGQMCOYVJQRIRROMPCQBLDYIGQO9AMORPYFSSUGACOJXGAQSPDY9YWRRPESNXXBDQ9OZOXVIOMLGTSWAMKMTDRSPGJKGBXQIVNRJRFRYEZ9VJDLHIKPSKMYC9YEGHFDS9SGVDHRIXBEMLFIINOHVPXIFAZCJKBHVMQZEVWCOSNWQRDYWVAIBLSCBGESJUIBWZECPUCAYAWMTQKRMCHONIPKJYYTEGZCJYCT9ABRWTJLRQXKMWY9GWZMHYZNWPXULNZAPVQLPMYQZCYNEPOCGOHBJUZLZDPIXVHLDMQYJUUBEDXXPXFLNRGIPWBRNQQZJSGSJTTYHIGGFAWJVXWL9THTPWOOHTNQWCNYOYZXALHAZXVMIZE9WMQUDCHDJMIBWKTYH9AC9AFOT9DPCADCV9ZWUTE9QNOMSZPTZDJLJZCJGHXUNBJFUBJWQUEZDMHXGBPTNSPZBR9TGSKVOHMOQSWPGFLSWNESFKSAZY9HHERAXALZCABFYPOVLAHMIHVDBGKUMDXC9WHHTIRYHZVWNXSVQUWCR9M9RAGMFEZZKZ9XEOQGOSLFQCHHOKLDSA9QCMDGCGMRYJZLBVIFOLBIJPROKMHOYTBTJIWUZWJMCTKCJKKTR9LCVYPVJI9AHGI9JOWMIWZAGMLDFJA9WU9QAMEFGABIBEZNNAL9OXSBFLOEHKDGHWFQSHMPLYFCNXAAZYJLMQDEYRGL9QKCEUEJ9LLVUOINVSZZQHCIKPAGMT9CAYIIMTTBCPKWTYHOJIIY9GYNPAJNUJ9BKYYXSV9JSPEXYMCFAIKTGNRSQGUNIYZCRT9FOWENSZQPD9ALUPYYAVICHVYELYFPUYDTWUSWNIYFXPX9MICCCOOZIWRNJIDALWGWRATGLJXNAYTNIZWQ9YTVDBOFZRKO9CFWRPAQQRXTPACOWCPRLYRYSJARRKSQPR9TCFXDVIXLP9XVL99ERRDSOHBFJDJQQGGGCZNDQ9NYCTQJWVZIAELCRBJJFDMCNZU9FIZRPGNURTXOCDSQGXTQHKHUECGWFUUYS9J9NYQ9U9P9UUP9YMZHWWWCIASCFLCMSKTELZWUGCDE9YOKVOVKTAYPHDF9ZCCQAYPJIJNGSHUIHHCOSSOOBUDOKE9CJZGYSSGNCQJVBEFTZFJ9SQUHOASKRRGBSHWKBCBWBTJHOGQ9WOMQFHWJVEG9NYX9KWBTCAIXNXHEBDIOFO9ALYMFGRICLCKKLG9FOBOX9PDWNQRGHBKHGKKRLWTBEQMCWQRLHAVYYZDIIPKVQTHYTWQMTOACXZOQCDTJTBAAUWXSGJF9PNQIJ9AJRUMUVCPWYVYVARKR9RKGOUHHNKNVGGPDDLGKPQNOYHNKAVVKCXWXOQPZNSLATUJT9AUWRMPPSWHSTTYDFAQDXOCYTZHOYYGAIM9CELMZ9AZPWB9MJXGHOKDNNSZVUDAGXTJJSSZCPZVPZBYNNTUQABSXQWZCHDQSLGK9UOHCFKBIBNETK999999999999999999999999999999999999999999999999999999999999999999999999999999999NOXDXXKUDWLOFJLIPQIBRBMGDYCPGDNLQOLQS99EQYKBIU9VHCJVIPFUYCQDNY9APGEVYLCENJIOBLWNB999999999XKBRHUD99C99999999NKZKEKWLDKMJCI9N9XQOLWEPAYWSH9999999999999999999999999KDDTGZLIPBNZKMLTOLOXQVNGLASESDQVPTXALEKRMIOHQLUHD9ELQDBQETS9QFGTYOYWLNTSKKMVJAUXSIROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999IROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999"}); given(). - contentType("application/json"). + contentType("application/json").header("X-IOTA-API-Version",1). body(gson.toJson(request)). when(). post("/"). @@ -309,7 +309,7 @@ public void shouldTestStoreTransactions() { request.put("trytes", new String[] {"BYSWEAUTWXHXZ9YBZISEK9LUHWGMHXCGEVNZHRLUWQFCUSDXZHOFHWHL9MQPVJXXZLIXPXPXF9KYEREFSKCPKYIIKPZVLHUTDFQKKVVBBN9ATTLPCNPJDWDEVIYYLGPZGCWXOBDXMLJC9VO9QXTTBLAXTTBFUAROYEGQIVB9MJWJKXJMCUPTWAUGFZBTZCSJVRBGMYXTVBDDS9MYUJCPZ9YDWWQNIPUAIJXXSNLKUBSCOIJPCLEFPOXFJREXQCUVUMKSDOVQGGHRNILCO9GNCLWFM9APMNMWYASHXQAYBEXF9QRIHIBHYEJOYHRQJAOKAQ9AJJFQ9WEIWIJOTZATIBOXQLBMIJU9PCGBLVDDVFP9CFFSXTDUXMEGOOFXWRTLFGV9XXMYWEMGQEEEDBTIJ9OJOXFAPFQXCDAXOUDMLVYRMRLUDBETOLRJQAEDDLNVIRQJUBZBO9CCFDHIX9MSQCWYAXJVWHCUPTRSXJDESISQPRKZAFKFRULCGVRSBLVFOPEYLEE99JD9SEBALQINPDAZHFAB9RNBH9AZWIJOTLBZVIEJIAYGMC9AZGNFWGRSWAXTYSXVROVNKCOQQIWGPNQZKHUNODGYADPYLZZZUQRTJRTODOUKAOITNOMWNGHJBBA99QUMBHRENGBHTH9KHUAOXBVIVDVYYZMSEYSJWIOGGXZVRGN999EEGQMCOYVJQRIRROMPCQBLDYIGQO9AMORPYFSSUGACOJXGAQSPDY9YWRRPESNXXBDQ9OZOXVIOMLGTSWAMKMTDRSPGJKGBXQIVNRJRFRYEZ9VJDLHIKPSKMYC9YEGHFDS9SGVDHRIXBEMLFIINOHVPXIFAZCJKBHVMQZEVWCOSNWQRDYWVAIBLSCBGESJUIBWZECPUCAYAWMTQKRMCHONIPKJYYTEGZCJYCT9ABRWTJLRQXKMWY9GWZMHYZNWPXULNZAPVQLPMYQZCYNEPOCGOHBJUZLZDPIXVHLDMQYJUUBEDXXPXFLNRGIPWBRNQQZJSGSJTTYHIGGFAWJVXWL9THTPWOOHTNQWCNYOYZXALHAZXVMIZE9WMQUDCHDJMIBWKTYH9AC9AFOT9DPCADCV9ZWUTE9QNOMSZPTZDJLJZCJGHXUNBJFUBJWQUEZDMHXGBPTNSPZBR9TGSKVOHMOQSWPGFLSWNESFKSAZY9HHERAXALZCABFYPOVLAHMIHVDBGKUMDXC9WHHTIRYHZVWNXSVQUWCR9M9RAGMFEZZKZ9XEOQGOSLFQCHHOKLDSA9QCMDGCGMRYJZLBVIFOLBIJPROKMHOYTBTJIWUZWJMCTKCJKKTR9LCVYPVJI9AHGI9JOWMIWZAGMLDFJA9WU9QAMEFGABIBEZNNAL9OXSBFLOEHKDGHWFQSHMPLYFCNXAAZYJLMQDEYRGL9QKCEUEJ9LLVUOINVSZZQHCIKPAGMT9CAYIIMTTBCPKWTYHOJIIY9GYNPAJNUJ9BKYYXSV9JSPEXYMCFAIKTGNRSQGUNIYZCRT9FOWENSZQPD9ALUPYYAVICHVYELYFPUYDTWUSWNIYFXPX9MICCCOOZIWRNJIDALWGWRATGLJXNAYTNIZWQ9YTVDBOFZRKO9CFWRPAQQRXTPACOWCPRLYRYSJARRKSQPR9TCFXDVIXLP9XVL99ERRDSOHBFJDJQQGGGCZNDQ9NYCTQJWVZIAELCRBJJFDMCNZU9FIZRPGNURTXOCDSQGXTQHKHUECGWFUUYS9J9NYQ9U9P9UUP9YMZHWWWCIASCFLCMSKTELZWUGCDE9YOKVOVKTAYPHDF9ZCCQAYPJIJNGSHUIHHCOSSOOBUDOKE9CJZGYSSGNCQJVBEFTZFJ9SQUHOASKRRGBSHWKBCBWBTJHOGQ9WOMQFHWJVEG9NYX9KWBTCAIXNXHEBDIOFO9ALYMFGRICLCKKLG9FOBOX9PDWNQRGHBKHGKKRLWTBEQMCWQRLHAVYYZDIIPKVQTHYTWQMTOACXZOQCDTJTBAAUWXSGJF9PNQIJ9AJRUMUVCPWYVYVARKR9RKGOUHHNKNVGGPDDLGKPQNOYHNKAVVKCXWXOQPZNSLATUJT9AUWRMPPSWHSTTYDFAQDXOCYTZHOYYGAIM9CELMZ9AZPWB9MJXGHOKDNNSZVUDAGXTJJSSZCPZVPZBYNNTUQABSXQWZCHDQSLGK9UOHCFKBIBNETK999999999999999999999999999999999999999999999999999999999999999999999999999999999NOXDXXKUDWLOFJLIPQIBRBMGDYCPGDNLQOLQS99EQYKBIU9VHCJVIPFUYCQDNY9APGEVYLCENJIOBLWNB999999999XKBRHUD99C99999999NKZKEKWLDKMJCI9N9XQOLWEPAYWSH9999999999999999999999999KDDTGZLIPBNZKMLTOLOXQVNGLASESDQVPTXALEKRMIOHQLUHD9ELQDBQETS9QFGTYOYWLNTSKKMVJAUXSIROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999IROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999"}); given(). - contentType("application/json"). + contentType("application/json").header("X-IOTA-API-Version",1). body(gson.toJson(request)). when(). post("/").