package qora import java math BigDecimal import java math BigInteger i

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
package qora
import java.math.BigDecimal
import java.math.BigInteger
import java.util.ArrayList
import java.util.Arrays
import ntp.NTP
import qora.account.PrivateKeyAccount
import qora.block.Block
import qora.block.BlockFactory
import qora.crypto.Crypto
import qora.transaction.Transaction
import settings.Settings
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
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 = _
private var cachedAccounts = List[PrivateKeyAccount]()
def addUnconfirmedTransaction(transaction:Transaction){
addUnconfirmedTransaction(DBSet.getInstance(), transaction)
}
def addUnconfirmedTransaction(db:DBSet, transaction:Transaction){
db.getTransactionMap.add(transaction)
}
def getUnconfirmedTransactions() = new ArrayList[Transaction](DBSet.getInstance().getTransactionMap.getValues)
private def getKnownAccounts() = {
//CHECK IF CACHING ENABLED
if(Settings.getInstance().isGeneratorKeyCachingEnabled){
val privateKeyAccounts = Controller.getInstance().getPrivateKeyAccounts
//IF ACCOUNTS EXISTS
if(privateKeyAccounts.size() > 0){
//CACHE ACCOUNTS
this.cachedAccounts = privateKeyAccounts.toList
}
this.cachedAccounts
} else {
Controller.getInstance().getPrivateKeyAccounts.toList
}
}
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 || !Arrays.equals(this.solvingBlock.getSignature(), 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
if(account.getGeneratingBalance(db) == BigDecimal.ZERO){
throw new IllegalStateException("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
}