mirror of
https://github.com/Qortal/qortal.git
synced 2025-08-01 14:41:23 +00:00
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
834fcd80d7 | ||
|
20e4a79130 | ||
|
d336200d75 | ||
|
e5bb3e2f0a | ||
|
5b2b2bab46 | ||
|
c17eea3ed9 | ||
|
83f4e2f5bf | ||
|
c8e7a00c08 | ||
|
190014cf96 | ||
|
385064e324 |
2
pom.xml
2
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>1.4.0</version>
|
||||
<version>1.4.1</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
|
@@ -21,18 +21,28 @@ public class HSQLDBPool extends JDBCPool {
|
||||
public Connection tryConnection() throws SQLException {
|
||||
for (int i = 0; i < states.length(); i++) {
|
||||
if (states.compareAndSet(i, RefState.available, RefState.allocated)) {
|
||||
return connections[i].getConnection();
|
||||
JDBCPooledConnection pooledConnection = connections[i];
|
||||
|
||||
if (pooledConnection == null)
|
||||
// Probably shutdown situation
|
||||
return null;
|
||||
|
||||
return pooledConnection.getConnection();
|
||||
}
|
||||
|
||||
if (states.compareAndSet(i, RefState.empty, RefState.allocated)) {
|
||||
try {
|
||||
JDBCPooledConnection connection = (JDBCPooledConnection) source.getPooledConnection();
|
||||
JDBCPooledConnection pooledConnection = (JDBCPooledConnection) source.getPooledConnection();
|
||||
|
||||
connection.addConnectionEventListener(this);
|
||||
connection.addStatementEventListener(this);
|
||||
connections[i] = connection;
|
||||
if (pooledConnection == null)
|
||||
// Probably shutdown situation
|
||||
return null;
|
||||
|
||||
return connections[i].getConnection();
|
||||
pooledConnection.addConnectionEventListener(this);
|
||||
pooledConnection.addStatementEventListener(this);
|
||||
connections[i] = pooledConnection;
|
||||
|
||||
return pooledConnection.getConnection();
|
||||
} catch (SQLException e) {
|
||||
states.set(i, RefState.empty);
|
||||
}
|
||||
|
@@ -118,6 +118,37 @@ public class CrossChainResource {
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/trade/{ataddress}")
|
||||
@Operation(
|
||||
summary = "Show detailed trade info",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = CrossChainTradeData.class
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.ADDRESS_UNKNOWN, ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE})
|
||||
public CrossChainTradeData getTrade(@PathParam("ataddress") String atAddress) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
if (atData == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN);
|
||||
|
||||
ACCT acct = SupportedBlockchain.getAcctByCodeHash(atData.getCodeHash());
|
||||
if (acct == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
||||
return acct.populateTradeData(repository, atData);
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/trades")
|
||||
@Operation(
|
||||
|
@@ -146,7 +146,11 @@ public class QortalATAPI extends API {
|
||||
String atAddress = this.atData.getATAddress();
|
||||
|
||||
int height = timestamp.blockHeight;
|
||||
int sequence = timestamp.transactionSequence + 1;
|
||||
int sequence = timestamp.transactionSequence;
|
||||
|
||||
if (state.getCurrentBlockHeight() < BlockChain.getInstance().getAtFindNextTransactionFixHeight())
|
||||
// Off-by-one bug still in effect
|
||||
sequence += 1;
|
||||
|
||||
ATRepository.NextTransactionInfo nextTransactionInfo;
|
||||
try {
|
||||
|
@@ -70,6 +70,7 @@ public class BlockChain {
|
||||
private GenesisBlock.GenesisInfo genesisInfo;
|
||||
|
||||
public enum FeatureTrigger {
|
||||
atFindNextTransactionFix;
|
||||
}
|
||||
|
||||
/** Map of which blockchain features are enabled when (height/timestamp) */
|
||||
@@ -371,6 +372,10 @@ public class BlockChain {
|
||||
|
||||
// Convenience methods for specific blockchain feature triggers
|
||||
|
||||
public int getAtFindNextTransactionFixHeight() {
|
||||
return this.featureTriggers.get(FeatureTrigger.atFindNextTransactionFix.name()).intValue();
|
||||
}
|
||||
|
||||
// More complex getters for aspects that change by height or timestamp
|
||||
|
||||
public long getRewardAtHeight(int ourHeight) {
|
||||
|
@@ -377,7 +377,9 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
// Process new keys
|
||||
} while (true);
|
||||
|
||||
return walletTransactions.stream().collect(Collectors.toList());
|
||||
Comparator<BitcoinyTransaction> newestTimestampFirstComparator = Comparator.comparingInt((BitcoinyTransaction txn) -> txn.timestamp).reversed();
|
||||
|
||||
return walletTransactions.stream().sorted(newestTimestampFirstComparator).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -475,6 +475,8 @@ public class Peer {
|
||||
ByteBuffer outputBuffer = ByteBuffer.wrap(message.toBytes());
|
||||
|
||||
synchronized (this.socketChannel) {
|
||||
final long sendStart = System.currentTimeMillis();
|
||||
|
||||
while (outputBuffer.hasRemaining()) {
|
||||
int bytesWritten = this.socketChannel.write(outputBuffer);
|
||||
|
||||
@@ -484,7 +486,7 @@ public class Peer {
|
||||
message.getId(),
|
||||
this));
|
||||
|
||||
if (bytesWritten == 0)
|
||||
if (bytesWritten == 0) {
|
||||
// Underlying socket's internal buffer probably full,
|
||||
// so wait a short while for bytes to actually be transmitted over the wire
|
||||
|
||||
@@ -496,6 +498,11 @@ public class Peer {
|
||||
* and connection loss.
|
||||
*/
|
||||
Thread.sleep(1L); //NOSONAR squid:S2276
|
||||
|
||||
if (System.currentTimeMillis() - sendStart > RESPONSE_TIMEOUT)
|
||||
// We've taken too long to send this message
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (MessageException e) {
|
||||
|
@@ -276,7 +276,7 @@ public class HSQLDBRepository implements Repository {
|
||||
return;
|
||||
}
|
||||
|
||||
try (Statement stmt = this.connection.createStatement()) {
|
||||
try {
|
||||
assertEmptyTransaction("connection close");
|
||||
|
||||
// Assume we are not going to be GC'd for a while
|
||||
|
@@ -234,10 +234,11 @@ public class PresenceTransaction extends Transaction {
|
||||
if (creatorsPresenceTransactions.isEmpty())
|
||||
return;
|
||||
|
||||
// List should contain oldest transaction first, so remove all but last from repository.
|
||||
creatorsPresenceTransactions.remove(creatorsPresenceTransactions.size() - 1);
|
||||
for (TransactionData transactionData : creatorsPresenceTransactions) {
|
||||
LOGGER.info(() -> String.format("Deleting older PRESENCE transaction %s", Base58.encode(transactionData.getSignature())));
|
||||
if (transactionData.getTimestamp() >= this.transactionData.getTimestamp())
|
||||
continue;
|
||||
|
||||
LOGGER.debug(() -> String.format("Deleting older PRESENCE transaction %s", Base58.encode(transactionData.getSignature())));
|
||||
this.repository.getTransactionRepository().delete(transactionData);
|
||||
}
|
||||
}
|
||||
|
@@ -814,6 +814,23 @@ public abstract class Transaction {
|
||||
|
||||
return ValidationResult.OK;
|
||||
} finally {
|
||||
/*
|
||||
* We call discardChanges() to restart repository 'transaction', discarding any
|
||||
* transactional table locks, hence reducing possibility of deadlock or
|
||||
* "serialization failure" with HSQLDB due to reads.
|
||||
*
|
||||
* "Serialization failure" most likely caused by existing transaction check above,
|
||||
* where multiple threads are importing transactions
|
||||
* and one thread finds existing an transaction, returns (unlocking blockchain lock),
|
||||
* then another thread immediately obtains lock, tries to delete above existing transaction
|
||||
* (e.g. older PRESENCE transaction) but can't because first thread's repository
|
||||
* session still has row-lock on existing transaction and hasn't yet closed
|
||||
* repository session. Deadlock caused by race condition.
|
||||
*
|
||||
* Hence we clear any repository-based locks before releasing blockchain lock.
|
||||
*/
|
||||
repository.discardChanges();
|
||||
|
||||
blockchainLock.unlock();
|
||||
}
|
||||
}
|
||||
|
@@ -125,8 +125,8 @@ public abstract class ExecuteProduceConsume implements Runnable {
|
||||
// It's possible this might need to become a class instance private volatile
|
||||
boolean canBlock = false;
|
||||
|
||||
while (true) {
|
||||
final Task task;
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
Task task = null;
|
||||
|
||||
this.logger.trace(() -> String.format("[%d] waiting to produce...", Thread.currentThread().getId()));
|
||||
|
||||
@@ -142,7 +142,16 @@ public abstract class ExecuteProduceConsume implements Runnable {
|
||||
Thread.currentThread().getId(), this.activeThreadCount, this.consumerCount, lambdaCanIdle));
|
||||
|
||||
final long beforeProduce = isLoggerTraceEnabled ? System.currentTimeMillis() : 0;
|
||||
task = produceTask(canBlock);
|
||||
|
||||
try {
|
||||
task = produceTask(canBlock);
|
||||
} catch (InterruptedException e) {
|
||||
// We're in shutdown situation so exit
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (Exception e) {
|
||||
this.logger.warn(() -> String.format("[%d] exception while trying to produce task", Thread.currentThread().getId()), e);
|
||||
}
|
||||
|
||||
this.logger.trace(() -> String.format("[%d] producing took %dms", Thread.currentThread().getId(), System.currentTimeMillis() - beforeProduce));
|
||||
}
|
||||
|
||||
@@ -155,7 +164,8 @@ public abstract class ExecuteProduceConsume implements Runnable {
|
||||
--this.activeThreadCount;
|
||||
this.logger.trace(() -> String.format("[%d] ending, activeThreadCount now: %d",
|
||||
Thread.currentThread().getId(), this.activeThreadCount));
|
||||
break;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// We're the last surviving thread - producer can afford to block next round
|
||||
@@ -192,7 +202,16 @@ public abstract class ExecuteProduceConsume implements Runnable {
|
||||
}
|
||||
|
||||
this.logger.trace(() -> String.format("[%d] performing task...", Thread.currentThread().getId()));
|
||||
task.perform(); // This can block for a while
|
||||
|
||||
try {
|
||||
task.perform(); // This can block for a while
|
||||
} catch (InterruptedException e) {
|
||||
// We're in shutdown situation so exit
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (Exception e) {
|
||||
this.logger.warn(() -> String.format("[%d] exception while performing task", Thread.currentThread().getId()), e);
|
||||
}
|
||||
|
||||
this.logger.trace(() -> String.format("[%d] finished task", Thread.currentThread().getId()));
|
||||
|
||||
synchronized (this) {
|
||||
@@ -206,8 +225,6 @@ public abstract class ExecuteProduceConsume implements Runnable {
|
||||
canBlock = false;
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// We're in shutdown situation so exit
|
||||
} finally {
|
||||
if (this.isLoggerTraceEnabled)
|
||||
Thread.currentThread().setName(this.className);
|
||||
|
@@ -48,6 +48,7 @@
|
||||
"minutesPerBlock": 1
|
||||
},
|
||||
"featureTriggers": {
|
||||
"atFindNextTransactionFix": 275000
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -77,16 +77,24 @@ public class GetNextTransactionTests extends Common {
|
||||
BlockUtils.mintBlock(repository);
|
||||
assertTimestamp(repository, atAddress, transaction);
|
||||
|
||||
// Mint a few blocks, then send non-AT message, followed by AT message
|
||||
// Mint a few blocks, then send non-AT message, followed by two AT messages (in same block)
|
||||
for (int i = 0; i < 5; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
sendMessage(repository, deployer, data, deployer.getAddress());
|
||||
transaction = sendMessage(repository, deployer, data, atAddress);
|
||||
|
||||
Transaction transaction1 = sendMessage(repository, deployer, data, atAddress);
|
||||
Transaction transaction2 = sendMessage(repository, deployer, data, atAddress);
|
||||
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Confirm AT finds message
|
||||
// Confirm AT finds first message
|
||||
BlockUtils.mintBlock(repository);
|
||||
assertTimestamp(repository, atAddress, transaction);
|
||||
assertTimestamp(repository, atAddress, transaction1);
|
||||
|
||||
// Confirm AT finds second message
|
||||
BlockUtils.mintBlock(repository);
|
||||
assertTimestamp(repository, atAddress, transaction2);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user