Last active
August 29, 2015 14:11
-
-
Save kushti/605dfe4f17d0dd75cedf to your computer and use it in GitHub Desktop.
QORA's BlockGenerator rewritten into Scala language
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package qora | |
import java.math.BigDecimal | |
import java.math.BigInteger | |
import ntp.NTP | |
import qora.account.PrivateKeyAccount | |
import qora.block.Block | |
import qora.block.BlockFactory | |
import qora.crypto.Crypto | |
import qora.transaction.Transaction | |
import com.google.common.primitives.Bytes | |
import com.google.common.primitives.Longs | |
import controller.Controller | |
import database.DBSet | |
import scala.collection.JavaConversions._ | |
import scala.collection.concurrent.TrieMap | |
/** | |
* Scala version of QORA's BlockGenerator | |
* | |
* There's one behavioral difference from original logic, please see comments to the | |
* addUnconfirmedTransactions method. But it should be compatible with QORA. | |
* | |
* | |
* kushti | |
*/ | |
// TODO: make code more functional, i.e. get off of mutable variables, while(true) etc | |
object BlockGenerator extends Thread { | |
val RETARGET = 10 | |
val MIN_BALANCE = 1L | |
val MAX_BALANCE = 10000000000L | |
val MIN_BLOCK_TIME = 1 * 60 | |
val MAX_BLOCK_TIME = 5 * 60 | |
private val blocks = TrieMap[PrivateKeyAccount, Block]() | |
private var solvingBlock: Block = _ | |
def addUnconfirmedTransaction(transaction: Transaction): Unit = | |
addUnconfirmedTransaction(DBSet.getInstance(), transaction) | |
def addUnconfirmedTransaction(db: DBSet, transaction: Transaction): Unit = | |
db.getTransactionMap.add(transaction) | |
def getUnconfirmedTransactions = DBSet.getInstance().getTransactionMap.getValues.toSeq | |
private def getKnownAccounts = Controller.getInstance().getPrivateKeyAccounts | |
override def run() { | |
while (true) { | |
//CHECK IF WE ARE UPTODATE | |
if (!Controller.getInstance().isUpToDate) { | |
Controller.getInstance().update() | |
} | |
//CHECK IF WE HAVE CONNECTIONS | |
if (Controller.getInstance().getStatus == Controller.STATUS_OKE) { | |
val lastBlockSignature = DBSet.getInstance().getBlockMap.getLastBlockSignature | |
//CHECK IF DIFFERENT FOR CURRENT SOLVING BLOCK | |
if (this.solvingBlock == null || !this.solvingBlock.getSignature.sameElements(lastBlockSignature)) { | |
//SET NEW BLOCK TO SOLVE | |
this.solvingBlock = DBSet.getInstance().getBlockMap.getLastBlock | |
//RESET BLOCKS | |
this.blocks.clear() | |
} | |
//GENERATE NEW BLOCKS | |
if (Controller.getInstance().doesWalletExists()) { | |
this.getKnownAccounts foreach { account => | |
if (account.getGeneratingBalance.compareTo(BigDecimal.ONE) >= 0) { | |
//CHECK IF BLOCK FROM USER ALREADY EXISTS USE MAP ACCOUNT BLOCK EASY | |
if (!this.blocks.containsKey(account)) { | |
//GENERATE NEW BLOCK FOR USER | |
blocks += account -> this.generateNextBlock(DBSet.getInstance(), account, this.solvingBlock) | |
} | |
} | |
} | |
} | |
//IS VALID BLOCK FOUND? | |
val validBlockFound = this.blocks.keySet.foldLeft(false) {case (found, account) => | |
val block = this.blocks.get(account).get | |
if (!found && block.getTimestamp <= NTP.getTime) { | |
//ADD TRANSACTIONS | |
this.addUnconfirmedTransactions(DBSet.getInstance(), block) | |
//ADD TRANSACTION SIGNATURE | |
block.setTransactionsSignature(this.calculateTransactionsSignature(block, account)) | |
//PASS BLOCK TO CONTROLLER | |
Controller.getInstance().newBlockGenerated(block) | |
true | |
} else false | |
} | |
if (!validBlockFound) Thread.sleep(100) | |
} else { | |
Thread.sleep(100) | |
} | |
} | |
} | |
def generateNextBlock(db: DBSet, account: PrivateKeyAccount, block: Block) = { | |
//CHECK IF ACCOUNT HAS BALANCE - but already checked before call (kushti) | |
require (account.getGeneratingBalance(db) != BigDecimal.ZERO, "Zero balance in generateNextBlock") | |
val signature = this.calculateSignature(db, block, account) | |
val hash = Crypto.getInstance().digest(signature) | |
val hashValue = new BigInteger(1, hash) | |
//CALCULATE ACCOUNT TARGET | |
val targetBytes = Array.fill(32)(Byte.MaxValue) | |
val baseTarget = BigInteger.valueOf(getBaseTarget(getNextBlockGeneratingBalance(db, block))) | |
val target = new BigInteger(1, targetBytes) | |
.divide(baseTarget) | |
.multiply(account.getGeneratingBalance(db).toBigInteger) //MULTIPLY TARGET BY USER BALANCE | |
//CALCULATE GUESSES | |
val guesses = hashValue.divide(target).add(BigInteger.ONE) | |
//CALCULATE TIMESTAMP | |
val timestampRaw = guesses.multiply(BigInteger.valueOf(1000)).add(BigInteger.valueOf(block.getTimestamp)) | |
//CHECK IF NOT HIGHER THAN MAX LONG VALUE | |
val timestamp = (if (timestampRaw.compareTo(BigInteger.valueOf(Long.MaxValue)) == 1) | |
BigInteger.valueOf(Long.MaxValue) | |
else timestampRaw).longValue() | |
val version = 1 | |
BlockFactory.getInstance().create(version, block.getSignature, timestamp, getNextBlockGeneratingBalance(db, block), account, signature) | |
} | |
def calculateSignature(db: DBSet, solvingBlock: Block, account: PrivateKeyAccount) = { | |
//WRITE PARENT GENERATOR SIGNATURE | |
val generatorSignature = Bytes.ensureCapacity(solvingBlock.getGeneratorSignature, Block.GENERATOR_SIGNATURE_LENGTH, 0) | |
//WRITE GENERATING BALANCE | |
val baseTargetBytesRaw = Longs.toByteArray(getNextBlockGeneratingBalance(db, solvingBlock)) | |
val baseTargetBytes = Bytes.ensureCapacity(baseTargetBytesRaw, Block.GENERATING_BALANCE_LENGTH, 0) | |
//WRITE GENERATOR | |
val generatorBytes = Bytes.ensureCapacity(account.getPublicKey, Block.GENERATOR_LENGTH, 0) | |
//CALC SIGNATURE OF NEWBLOCKHEADER | |
Crypto.getInstance().sign(account, Bytes.concat(generatorSignature, baseTargetBytes, generatorBytes)) | |
} | |
def calculateTransactionsSignature(block: Block, account: PrivateKeyAccount) = { | |
val data = block.getTransactions.foldLeft(block.getGeneratorSignature) { case (bytes, tx) => | |
Bytes.concat(bytes, tx.getSignature); | |
} | |
Crypto.getInstance().sign(account, data) | |
} | |
def addUnconfirmedTransactions(db: DBSet, block: Block) = { | |
//CREATE FORK OF GIVEN DATABASE | |
val newBlockDb = db.fork() | |
//ORDER TRANSACTIONS BY FEE PER BYTE | |
val orderedTransactions = db.getTransactionMap.getValues.toSeq.sortBy(_.feePerByte()) | |
/* warning: simplification here! | |
QORA does break after first transaction matched conditions then repeat cycle | |
(while orderedTransactions contains transactions to process) | |
*/ | |
orderedTransactions.foldLeft(0) { case (totalBytes, tx) => | |
if (tx.getTimestamp <= block.getTimestamp && tx.getDeadline > block.getTimestamp | |
&& tx.isValid(newBlockDb) == Transaction.VALIDATE_OKE | |
&& totalBytes + tx.getDataLength <= Block.MAX_TRANSACTION_BYTES) { | |
block.addTransaction(tx) | |
tx.process(newBlockDb) | |
totalBytes + tx.getDataLength | |
} else totalBytes | |
} | |
} | |
def getNextBlockGeneratingBalance(db: DBSet, block: Block) = { | |
if (block.getHeight(db) % RETARGET == 0) { | |
//GET FIRST BLOCK OF TARGET | |
val firstBlock = (1 to RETARGET - 1).foldLeft(block) { case (bl, _) => bl.getParent(db)} | |
//CALCULATE THE GENERATING TIME FOR LAST 10 BLOCKS | |
val generatingTime = block.getTimestamp - firstBlock.getTimestamp | |
//CALCULATE EXPECTED FORGING TIME | |
val expectedGeneratingTime = getBlockTime(block.getGeneratingBalance) * RETARGET * 1000 | |
//CALCULATE MULTIPLIER | |
val multiplier = expectedGeneratingTime / generatingTime.toDouble | |
//CALCULATE NEW GENERATING BALANCE | |
val generatingBalance = (block.getGeneratingBalance * multiplier).toLong | |
minMaxBalance(generatingBalance) | |
} else block.getGeneratingBalance | |
} | |
def getBaseTarget(generatingBalance: Long) = minMaxBalance(generatingBalance) * getBlockTime(generatingBalance) | |
def getBlockTime(generatingBalance: Long) = { | |
val percentageOfTotal = minMaxBalance(generatingBalance) / MAX_BALANCE.toDouble | |
(MIN_BLOCK_TIME + ((MAX_BLOCK_TIME - MIN_BLOCK_TIME) * (1 - percentageOfTotal))).toLong | |
} | |
def minMaxBalance(generatingBalance: Long) = | |
if (generatingBalance < MIN_BALANCE) MIN_BALANCE | |
else if (generatingBalance > MAX_BALANCE) MAX_BALANCE | |
else generatingBalance | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment