diff --git a/scorex-basics/src/main/scala/scorex/account/Account.scala b/scorex-basics/src/main/scala/scorex/account/Account.scala index aa674648..0e044d16 100644 --- a/scorex-basics/src/main/scala/scorex/account/Account.scala +++ b/scorex-basics/src/main/scala/scorex/account/Account.scala @@ -7,7 +7,7 @@ import scorex.utils.ScorexLogging import scala.util.Try - +@SerialVersionUID(-5326597598126993189L) class Account(val address: String) extends Serializable { lazy val bytes = Base58.decode(address).get diff --git a/scorex-basics/src/main/scala/scorex/account/PrivateKeyAccount.scala b/scorex-basics/src/main/scala/scorex/account/PrivateKeyAccount.scala index e7960a5e..4da72ce2 100644 --- a/scorex-basics/src/main/scala/scorex/account/PrivateKeyAccount.scala +++ b/scorex-basics/src/main/scala/scorex/account/PrivateKeyAccount.scala @@ -2,6 +2,7 @@ package scorex.account import scorex.crypto.EllipticCurveImpl +@SerialVersionUID(8568952246932352318L) case class PrivateKeyAccount(seed: Array[Byte], privateKey: Array[Byte], override val publicKey: Array[Byte]) extends PublicKeyAccount(publicKey) { diff --git a/scorex-basics/src/main/scala/scorex/account/PublicKeyAccount.scala b/scorex-basics/src/main/scala/scorex/account/PublicKeyAccount.scala index 1178cb5f..5f62f189 100644 --- a/scorex-basics/src/main/scala/scorex/account/PublicKeyAccount.scala +++ b/scorex-basics/src/main/scala/scorex/account/PublicKeyAccount.scala @@ -1,4 +1,4 @@ package scorex.account - +@SerialVersionUID(-5511437096393374460L) class PublicKeyAccount(val publicKey: Array[Byte]) extends Account(Account.fromPublicKey(publicKey)) diff --git a/scorex-basics/src/main/scala/scorex/network/peer/PeerDatabase.scala b/scorex-basics/src/main/scala/scorex/network/peer/PeerDatabase.scala index 7695f8e7..8d23317c 100644 --- a/scorex-basics/src/main/scala/scorex/network/peer/PeerDatabase.scala +++ b/scorex-basics/src/main/scala/scorex/network/peer/PeerDatabase.scala @@ -3,6 +3,7 @@ package scorex.network.peer import java.net.InetSocketAddress //todo: add optional nonce +@SerialVersionUID(-8490103514095092419L) case class PeerInfo(lastSeen: Long, nonce: Option[Long] = None, nodeName: Option[String] = None) trait PeerDatabase { diff --git a/scorex-basics/src/main/scala/scorex/network/peer/PeerDatabaseImpl.scala b/scorex-basics/src/main/scala/scorex/network/peer/PeerDatabaseImpl.scala index 231f97c0..9ee07019 100644 --- a/scorex-basics/src/main/scala/scorex/network/peer/PeerDatabaseImpl.scala +++ b/scorex-basics/src/main/scala/scorex/network/peer/PeerDatabaseImpl.scala @@ -19,6 +19,7 @@ class PeerDatabaseImpl(settings: Settings, filename: Option[String]) extends Pee private val blacklist: MVMap[String, Long] = database.openMap("blacklist") private lazy val ownNonce = settings.nodeNonce + private lazy val blacklistResidenceTimeMilliseconds = settings.blacklistResidenceTimeMilliseconds override def addOrUpdateKnownPeer(address: InetSocketAddress, peerInfo: PeerInfo): Unit = { val updatedPeerInfo = Option(whitelistPersistence.get(address)).map { case dbPeerInfo => @@ -27,16 +28,19 @@ class PeerDatabaseImpl(settings: Settings, filename: Option[String]) extends Pee PeerInfo(peerInfo.lastSeen, nonceOpt, nodeNameOpt) }.getOrElse(peerInfo) whitelistPersistence.put(address, updatedPeerInfo) + updateBlacklist() database.commit() } override def blacklistPeer(address: InetSocketAddress): Unit = { whitelistPersistence.remove(address) if (!isBlacklisted(address)) blacklist += address.getHostName -> System.currentTimeMillis() + else blacklist(address.getHostName) = System.currentTimeMillis() database.commit() } override def isBlacklisted(address: InetSocketAddress): Boolean = { + updateBlacklist() blacklist.synchronized(blacklist.contains(address.getHostName)) } @@ -48,4 +52,11 @@ class PeerDatabaseImpl(settings: Settings, filename: Option[String]) extends Pee override def blacklistedPeers(): Seq[String] = blacklist.keys.toSeq + private def updateBlacklist(): Unit = { + val current = System.currentTimeMillis + blacklist.synchronized({ + val toRemove = blacklist.filter(b => b._2 < current - blacklistResidenceTimeMilliseconds).keys + toRemove.foreach(k => blacklist.synchronized(blacklist.remove(k))) + }) + } } \ No newline at end of file diff --git a/scorex-basics/src/main/scala/scorex/settings/Settings.scala b/scorex-basics/src/main/scala/scorex/settings/Settings.scala index 98c49042..a2a7cb9a 100644 --- a/scorex-basics/src/main/scala/scorex/settings/Settings.scala +++ b/scorex-basics/src/main/scala/scorex/settings/Settings.scala @@ -5,15 +5,14 @@ import java.net.InetSocketAddress import play.api.libs.json.{JsObject, Json} import scorex.crypto.encode.Base58 -import scorex.crypto.hash.CryptographicHash.Digest import scorex.utils.ScorexLogging import scala.concurrent.duration._ import scala.util.{Random, Try} /** - * Settings - */ + * Settings + */ trait Settings extends ScorexLogging { @@ -56,6 +55,12 @@ trait Settings extends ScorexLogging { lazy val nodeName = (p2pSettings \ "nodeName").asOpt[String] .getOrElse(Random.nextPrintableChar().toString + nodeNonce) + private val DefaultBlacklistResidenceTimeMilliseconds: Long = 10 * 60 * 1000 + + lazy val blacklistResidenceTimeMilliseconds: Long = + (p2pSettings \ "blacklistResidenceTimeMilliseconds").asOpt[Long] + .getOrElse(DefaultBlacklistResidenceTimeMilliseconds) + lazy val localOnly = (p2pSettings \ "localOnly").asOpt[Boolean].getOrElse(false) lazy val knownPeers = Try { diff --git a/scorex-basics/src/main/scala/scorex/utils/ScorexLogging.scala b/scorex-basics/src/main/scala/scorex/utils/ScorexLogging.scala index 081a2624..92abf069 100644 --- a/scorex-basics/src/main/scala/scorex/utils/ScorexLogging.scala +++ b/scorex-basics/src/main/scala/scorex/utils/ScorexLogging.scala @@ -1,7 +1,49 @@ package scorex.utils -import org.slf4j.LoggerFactory +import org.slf4j.{Logger, LoggerFactory} + +case class LoggerFacade(logger: Logger) { + def trace(message: => String): Unit = { + if (logger.isTraceEnabled) + logger.trace(message) + } + + def debug(message: => String): Unit = { + if (logger.isDebugEnabled) + logger.debug(message) + } + + def info(message: => String): Unit = { + if (logger.isInfoEnabled) + logger.info(message) + } + + def info(message: => String, throwable: Throwable): Unit = { + if (logger.isInfoEnabled) + logger.info(message, throwable) + } + + def warn(message: => String): Unit = { + if (logger.isWarnEnabled) + logger.warn(message) + } + + def warn(message: => String, throwable: Throwable): Unit = { + if (logger.isWarnEnabled) + logger.warn(message, throwable) + } + + def error(message: => String): Unit = { + if (logger.isErrorEnabled) + logger.error(message) + } + + def error(message: => String, throwable: Throwable): Unit = { + if (logger.isErrorEnabled) + logger.error(message, throwable) + } +} trait ScorexLogging { - protected def log = LoggerFactory.getLogger(this.getClass) + protected def log = LoggerFacade(LoggerFactory.getLogger(this.getClass)) } diff --git a/scorex-basics/src/test/scala/scorex/network/BlacklistParallelSpecification.scala b/scorex-basics/src/test/scala/scorex/network/BlacklistParallelSpecification.scala new file mode 100644 index 00000000..c8ddb7d4 --- /dev/null +++ b/scorex-basics/src/test/scala/scorex/network/BlacklistParallelSpecification.scala @@ -0,0 +1,106 @@ +package scorex.network + +import java.net.{InetAddress, InetSocketAddress} + +import org.scalatest.{FeatureSpec, GivenWhenThen, ParallelTestExecution} +import play.api.libs.json.{JsObject, Json} +import scorex.network.peer.{PeerDatabaseImpl, PeerInfo} +import scorex.settings.Settings + +class BlacklistParallelSpecification extends FeatureSpec with GivenWhenThen with ParallelTestExecution { + + object TestSettings extends Settings { + override lazy val settingsJSON: JsObject = Json.obj() + override val filename: String = "" + override lazy val blacklistResidenceTimeMilliseconds = 1000L + } + + info("As a Peer") + info("I want to blacklist other peers for certain time") + info("So I can give them another chance after") + + feature("Blacklist") { + scenario("Peer blacklist another peer") { + + Given("Peer database is empty") + val peerDatabase = new PeerDatabaseImpl(TestSettings, None) + assert(peerDatabase.knownPeers(false).isEmpty) + assert(peerDatabase.blacklistedPeers().isEmpty) + + When("Peer adds another peer to whitelist") + val anotherPeer = new PeerInfo(System.currentTimeMillis) + val port: Int = 1234 + val address = new InetSocketAddress(InetAddress.getByName("ya.ru"), port) + peerDatabase.addOrUpdateKnownPeer(address, anotherPeer) + assert(peerDatabase.knownPeers(false).contains(address)) + assert(!peerDatabase.blacklistedPeers().contains(address.getHostName)) + + And("Peer blacklists another peer") + peerDatabase.blacklistPeer(address) + assert(peerDatabase.isBlacklisted(address)) + assert(peerDatabase.blacklistedPeers().contains(address.getHostName)) + assert(!peerDatabase.knownPeers(false).contains(address)) + + And("Peer waits for some time") + Thread.sleep(TestSettings.blacklistResidenceTimeMilliseconds) + + Then("Another peer disappear from blacklist") + assert(!peerDatabase.isBlacklisted(address)) + + And("Another peer still not in whitelist") + assert(!peerDatabase.knownPeers(false).contains(address)) + } + + scenario("Peer blacklist few peers") { + + Given("Peer database is empty") + val peerDatabase = new PeerDatabaseImpl(TestSettings, None) + assert(peerDatabase.knownPeers(false).isEmpty) + assert(peerDatabase.blacklistedPeers().isEmpty) + + When("Peer adds other peers") + val anotherPeer = new PeerInfo(System.currentTimeMillis) + val port: Int = 1234 + val address1 = new InetSocketAddress(InetAddress.getByName("bing.com"), port) + val address2 = new InetSocketAddress(InetAddress.getByName("google.com"), port) + val address3 = new InetSocketAddress(InetAddress.getByName("yandex.ru"), port) + peerDatabase.addOrUpdateKnownPeer(address1, anotherPeer) + peerDatabase.addOrUpdateKnownPeer(address2, anotherPeer) + peerDatabase.addOrUpdateKnownPeer(address3, anotherPeer) + assert(!peerDatabase.isBlacklisted(address1)) + assert(!peerDatabase.isBlacklisted(address2)) + assert(!peerDatabase.isBlacklisted(address3)) + + And("Peer blacklists other peers") + peerDatabase.blacklistPeer(address1) + peerDatabase.blacklistPeer(address2) + peerDatabase.blacklistPeer(address3) + assert(peerDatabase.isBlacklisted(address1)) + assert(peerDatabase.isBlacklisted(address2)) + assert(peerDatabase.isBlacklisted(address3)) + + And("Peer waits half period") + Thread.sleep(TestSettings.blacklistResidenceTimeMilliseconds / 2) + + And("Adds one peer to blacklist one more time") + peerDatabase.blacklistPeer(address2) + + And("Waits another half of period") + Thread.sleep(TestSettings.blacklistResidenceTimeMilliseconds / 2) + + Then("Two peers disappear from blacklist") + assert(!peerDatabase.isBlacklisted(address1)) + assert(peerDatabase.isBlacklisted(address2)) + assert(!peerDatabase.isBlacklisted(address3)) + + And("Then waits another half of period") + Thread.sleep(TestSettings.blacklistResidenceTimeMilliseconds / 2) + + And("All peers not in blacklist") + assert(!peerDatabase.isBlacklisted(address1)) + assert(!peerDatabase.isBlacklisted(address2)) + assert(!peerDatabase.isBlacklisted(address3)) + } + } + +} diff --git a/scorex-basics/src/test/scala/scorex/network/BlacklistSpecification.scala b/scorex-basics/src/test/scala/scorex/network/BlacklistSpecification.scala new file mode 100644 index 00000000..3b9b2398 --- /dev/null +++ b/scorex-basics/src/test/scala/scorex/network/BlacklistSpecification.scala @@ -0,0 +1,54 @@ +package scorex.network + +import java.net.{InetAddress, InetSocketAddress} + +import org.scalatest.{FeatureSpec, GivenWhenThen} +import play.api.libs.json.{JsObject, Json} +import scorex.network.peer.{PeerDatabaseImpl, PeerInfo} +import scorex.settings.Settings + +class BlacklistSpecification extends FeatureSpec with GivenWhenThen { + + object TestSettings extends Settings { + override lazy val settingsJSON: JsObject = Json.obj() + override val filename: String = "" + override lazy val blacklistResidenceTimeMilliseconds = 1000L + } + + info("As a Peer") + info("I want to blacklist other peers for certain time") + info("So I can give them another chance after") + + feature("Blacklist") { + scenario("Peer blacklist another peer") { + + Given("Peer database is empty") + val peerDatabase = new PeerDatabaseImpl(TestSettings, None) + assert(peerDatabase.knownPeers(false).isEmpty) + assert(peerDatabase.blacklistedPeers().isEmpty) + + When("Peer adds another peer to whitelist") + val anotherPeer = new PeerInfo(System.currentTimeMillis) + val port: Int = 1234 + val address = new InetSocketAddress(InetAddress.getByName("localhost"), port) + peerDatabase.addOrUpdateKnownPeer(address, anotherPeer) + assert(peerDatabase.knownPeers(false).contains(address)) + assert(!peerDatabase.blacklistedPeers().contains(address.getHostName)) + + And("Peer blacklists another peer") + peerDatabase.blacklistPeer(address) + assert(peerDatabase.isBlacklisted(address)) + assert(peerDatabase.blacklistedPeers().contains(address.getHostName)) + assert(!peerDatabase.knownPeers(false).contains(address)) + + And("Peer waits for some time") + Thread.sleep(TestSettings.blacklistResidenceTimeMilliseconds) + + Then("Another peer disappear from blacklist") + assert(!peerDatabase.isBlacklisted(address)) + + And("Another peer still not in whitelist") + assert(!peerDatabase.knownPeers(false).contains(address)) + } + } +} diff --git a/scorex-transaction/src/main/scala/scorex/transaction/PaymentTransaction.scala b/scorex-transaction/src/main/scala/scorex/transaction/PaymentTransaction.scala index 800dd98e..3579b5d6 100644 --- a/scorex-transaction/src/main/scala/scorex/transaction/PaymentTransaction.scala +++ b/scorex-transaction/src/main/scala/scorex/transaction/PaymentTransaction.scala @@ -12,6 +12,7 @@ import scorex.transaction.LagonakiTransaction.TransactionType import scala.util.{Failure, Try} +@SerialVersionUID(-4989881425715590828L) class PaymentTransaction(val sender: PublicKeyAccount, override val recipient: Account, override val amount: Long, diff --git a/scorex-transaction/src/main/scala/scorex/transaction/SimpleTransactionModule.scala b/scorex-transaction/src/main/scala/scorex/transaction/SimpleTransactionModule.scala index 5866a9c4..e0ec6f4f 100644 --- a/scorex-transaction/src/main/scala/scorex/transaction/SimpleTransactionModule.scala +++ b/scorex-transaction/src/main/scala/scorex/transaction/SimpleTransactionModule.scala @@ -3,7 +3,7 @@ package scorex.transaction import com.google.common.primitives.{Bytes, Ints} import org.h2.mvstore.MVStore import play.api.libs.json.{JsArray, JsObject, Json} -import scorex.account.{Account, PrivateKeyAccount, PublicKeyAccount} +import scorex.account.{Account, PrivateKeyAccount} import scorex.app.Application import scorex.block.{Block, BlockField} import scorex.network.message.Message @@ -11,7 +11,7 @@ import scorex.network.{Broadcast, NetworkController, TransactionalMessagesRepo} import scorex.settings.Settings import scorex.transaction.SimpleTransactionModule.StoredInBlock import scorex.transaction.state.database.UnconfirmedTransactionsDatabaseImpl -import scorex.transaction.state.database.blockchain.{StoredState, StoredBlockTree, StoredBlockchain} +import scorex.transaction.state.database.blockchain.{StoredBlockTree, StoredBlockchain, StoredState} import scorex.transaction.state.wallet.Payment import scorex.utils._ import scorex.wallet.Wallet @@ -19,6 +19,7 @@ import scorex.wallet.Wallet import scala.concurrent.duration._ import scala.util.Try +@SerialVersionUID(3044437555808662124L) case class TransactionsBlockField(override val value: Seq[Transaction]) extends BlockField[Seq[Transaction]] { diff --git a/scorex-transaction/src/main/scala/scorex/transaction/state/database/state/AccState.scala b/scorex-transaction/src/main/scala/scorex/transaction/state/database/state/AccState.scala index a42413ff..8f545506 100644 --- a/scorex-transaction/src/main/scala/scorex/transaction/state/database/state/AccState.scala +++ b/scorex-transaction/src/main/scala/scorex/transaction/state/database/state/AccState.scala @@ -1,3 +1,4 @@ package scorex.transaction.state.database.state +@SerialVersionUID(5655285204140981736L) case class AccState(balance: Long) \ No newline at end of file diff --git a/scorex-transaction/src/main/scala/scorex/transaction/state/database/state/Row.scala b/scorex-transaction/src/main/scala/scorex/transaction/state/database/state/Row.scala index 41dd4d9d..05cc7d8f 100644 --- a/scorex-transaction/src/main/scala/scorex/transaction/state/database/state/Row.scala +++ b/scorex-transaction/src/main/scala/scorex/transaction/state/database/state/Row.scala @@ -8,6 +8,7 @@ import org.h2.mvstore.`type`.DataType import scorex.serialization.BytesSerializable import scorex.transaction.{FeesStateChange, LagonakiTransaction, StateChangeReason} +@SerialVersionUID(-3499112732510272830L) case class Row(state: AccState, reason: Reason, lastRowHeight: Int) extends DataType with BytesSerializable { lazy val bytes: Array[Byte] = Ints.toByteArray(lastRowHeight) ++ diff --git a/scorex-transaction/src/test/scala/scorex/transaction/RowSpecification.scala b/scorex-transaction/src/test/scala/scorex/transaction/RowSpecification.scala index 8710a1ab..4a3d25a1 100644 --- a/scorex-transaction/src/test/scala/scorex/transaction/RowSpecification.scala +++ b/scorex-transaction/src/test/scala/scorex/transaction/RowSpecification.scala @@ -4,17 +4,23 @@ import java.io._ import org.scalacheck.Gen import org.scalatest.prop.{GeneratorDrivenPropertyChecks, PropertyChecks} -import org.scalatest.{Matchers, PropSpec} +import org.scalatest.{BeforeAndAfterAll, Matchers, PropSpec} import scorex.transaction.state.database.state._ class RowSpecification extends PropSpec with PropertyChecks with GeneratorDrivenPropertyChecks with Matchers - with TransactionGen { + with TransactionGen + with BeforeAndAfterAll { val FileName = "object.data" + override def afterAll(): Unit = { + val testDataFile = new File(FileName) + if (testDataFile.exists) testDataFile.delete + } + property("Row serialize and deserialize") { forAll(paymentGenerator, Gen.posNum[Long], Gen.posNum[Long], Gen.posNum[Int]) { (payment: PaymentTransaction, balance: Long,