Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 132 additions & 62 deletions src/brs/Block.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@

//TODO: Create JavaDocs and remove this
@SuppressWarnings({ "checkstyle:MissingJavadocTypeCheck", "checkstyle:MissingJavadocMethodCheck" })

public class Block {

private static final Logger logger = LoggerFactory.getLogger(Block.class);

private final int version;
private final int timestamp;
private final long previousBlockId;
Expand All @@ -41,24 +40,24 @@ public class Block {
private final byte[] generationSignature;
private final byte[] payloadHash;
private final AtomicReference<List<Transaction>> blockTransactions = new AtomicReference<>();

private final AtomicReference<List<Transaction>> allBlockTransactions = new AtomicReference<>();
private final AtomicReference<byte[]> cachedBytes = new AtomicReference<>();
private final AtomicReference<JsonObject> cachedJsonObject = new AtomicReference<>();
private List<Transaction> atTransactions = new ArrayList<>();
private List<Transaction> subscriptionTransactions = new ArrayList<>();
private List<Transaction> escrowTransactions = new ArrayList<>();
private byte[] blockSignature;

private BigInteger cumulativeDifficulty = BigInteger.ZERO;

private long baseTarget = Constants.INITIAL_BASE_TARGET;
private final AtomicLong nextBlockId = new AtomicLong();
private int height = -1;
private final AtomicLong id = new AtomicLong();
private final AtomicReference<String> stringId = new AtomicReference<>();
private final AtomicLong generatorId = new AtomicLong();
private long nonce;

private BigInteger pocTime = null;
private long commitment = 0L;

private final byte[] blockAts;

private Peer downloadedFrom = null;
private int byteLength = 0;

Expand All @@ -82,7 +81,6 @@ public class Block {
int height,
long baseTarget)
throws SignumException.ValidationException {

if (payloadLength > Signum.getFluxCapacitor().getValue(
FluxValues.MAX_PAYLOAD_LENGTH, height)
|| payloadLength < 0) {
Expand All @@ -91,7 +89,6 @@ public class Block {
+ payloadLength + " height " + height + "previd "
+ previousBlockId);
}

this.version = version;
this.timestamp = timestamp;
this.previousBlockId = previousBlockId;
Expand All @@ -104,7 +101,6 @@ public class Block {
this.generatorPublicKey = generatorPublicKey;
this.generationSignature = generationSignature;
this.blockSignature = blockSignature;

this.previousBlockHash = previousBlockHash;
if (transactions != null) {
this.blockTransactions.set(Collections.unmodifiableList(transactions));
Expand Down Expand Up @@ -151,7 +147,6 @@ public Block(
long nonce,
byte[] blockAts)
throws SignumException.ValidationException {

this(
version,
timestamp,
Expand All @@ -171,7 +166,6 @@ public Block(
blockAts,
height,
baseTarget);

this.cumulativeDifficulty = cumulativeDifficulty == null
? BigInteger.ZERO
: cumulativeDifficulty;
Expand Down Expand Up @@ -261,17 +255,59 @@ public byte[] getBlockSignature() {
}

public List<Transaction> getTransactions() {
if (blockTransactions.get() == null) {
this.blockTransactions
.set(Collections.unmodifiableList(
transactionDb().findBlockTransactions(getId(), true)));
this.blockTransactions.get().forEach(transaction -> transaction.setBlock(this));
List<Transaction> transactions = blockTransactions.get();
if (transactions == null) {
synchronized (this) {
transactions = blockTransactions.get();
if (transactions == null) {
List<Transaction> newTransactions = transactionDb().findBlockTransactions(getId(), true);
newTransactions.forEach(transaction -> transaction.setBlock(this));
transactions = Collections.unmodifiableList(newTransactions);
blockTransactions.set(transactions);
}
}
}
return blockTransactions.get();
return transactions;
}

public List<Transaction> getAllTransactions() {
return Collections.unmodifiableList(transactionDb().findBlockTransactions(getId(), false));
List<Transaction> transactions = allBlockTransactions.get();
if (transactions == null) {
synchronized (this) {
transactions = allBlockTransactions.get();
if (transactions == null) {
List<Transaction> newTransactions = transactionDb().findBlockTransactions(getId(), false);
newTransactions.forEach(transaction -> transaction.setBlock(this));
transactions = Collections.unmodifiableList(newTransactions);
allBlockTransactions.set(transactions);
}
}
}
return transactions;
}

public void setAtTransactions(List<Transaction> transactions) {
this.atTransactions = transactions;
}

public List<Transaction> getAtTransactions() {
return Collections.unmodifiableList(this.atTransactions);
}

public void setSubscriptionTransactions(List<Transaction> transactions) {
this.subscriptionTransactions = transactions;
}

public List<Transaction> getSubscriptionTransactions() {
return Collections.unmodifiableList(this.subscriptionTransactions);
}

public void setEscrowTransactions(List<Transaction> transactions) {
this.escrowTransactions = transactions;
}

public List<Transaction> getEscrowTransactions() {
return Collections.unmodifiableList(this.escrowTransactions);
}

public long getBaseTarget() {
Expand Down Expand Up @@ -367,29 +403,36 @@ public int hashCode() {
}

public JsonObject getJsonObject() {
JsonObject json = new JsonObject();
json.addProperty("version", version);
json.addProperty("timestamp", timestamp);
json.addProperty("previousBlock", Convert.toUnsignedLong(previousBlockId));
json.addProperty("totalAmountNQT", totalAmountNqt);
json.addProperty("totalFeeNQT", totalFeeNqt);
json.addProperty("totalFeeCashBackNQT", totalFeeCashBackNqt);
json.addProperty("totalFeeBurntNQT", totalFeeBurntNqt);
json.addProperty("payloadLength", payloadLength);
json.addProperty("payloadHash", Convert.toHexString(payloadHash));
json.addProperty("generatorPublicKey", Convert.toHexString(generatorPublicKey));
json.addProperty("generationSignature", Convert.toHexString(generationSignature));
if (version > 1) {
json.addProperty("previousBlockHash", Convert.toHexString(previousBlockHash));
if (cachedJsonObject.get() == null) {
synchronized (this) {
if (cachedJsonObject.get() == null) {
JsonObject json = new JsonObject();
json.addProperty("version", version);
json.addProperty("timestamp", timestamp);
json.addProperty("previousBlock", Convert.toUnsignedLong(previousBlockId));
json.addProperty("totalAmountNQT", totalAmountNqt);
json.addProperty("totalFeeNQT", totalFeeNqt);
json.addProperty("totalFeeCashBackNQT", totalFeeCashBackNqt);
json.addProperty("totalFeeBurntNQT", totalFeeBurntNqt);
json.addProperty("payloadLength", payloadLength);
json.addProperty("payloadHash", Convert.toHexString(payloadHash));
json.addProperty("generatorPublicKey", Convert.toHexString(generatorPublicKey));
json.addProperty("generationSignature", Convert.toHexString(generationSignature));
if (version > 1) {
json.addProperty("previousBlockHash", Convert.toHexString(previousBlockHash));
}
json.addProperty("blockSignature", Convert.toHexString(blockSignature));
JsonArray transactionsData = new JsonArray();
getTransactions().forEach(transaction -> transactionsData.add(transaction.getJsonObject()));
json.add("transactions", transactionsData);
json.addProperty("nonce", Convert.toUnsignedLong(nonce));
json.addProperty("baseTarget", Convert.toUnsignedLong(baseTarget));
json.addProperty("blockATs", Convert.toHexString(blockAts));
cachedJsonObject.set(json);
}
}
}
json.addProperty("blockSignature", Convert.toHexString(blockSignature));
JsonArray transactionsData = new JsonArray();
getTransactions().forEach(transaction -> transactionsData.add(transaction.getJsonObject()));
json.add("transactions", transactionsData);
json.addProperty("nonce", Convert.toUnsignedLong(nonce));
json.addProperty("baseTarget", Convert.toUnsignedLong(baseTarget));
json.addProperty("blockATs", Convert.toHexString(blockAts));
return json;
return cachedJsonObject.get();
}

// TODO: See about removing this check suppression:
Expand Down Expand Up @@ -425,15 +468,12 @@ static Block parseBlock(JsonObject blockData, int height)
long nonce = Convert.parseUnsignedLong(JSON.getAsString(blockData.get("nonce")));
long baseTarget = Convert.parseUnsignedLong(
JSON.getAsString(blockData.get("baseTarget")));

if (Signum.getFluxCapacitor().getValue(
FluxValues.POC_PLUS, height) && baseTarget == 0L) {
throw new SignumException.NotValidException("Block received without a baseTarget");
}

SortedMap<Long, Transaction> blockTransactions = new TreeMap<>();
JsonArray transactionsData = JSON.getAsJsonArray(blockData.get("transactions"));

for (JsonElement transactionData : transactionsData) {
Transaction transaction = Transaction.parseTransaction(
JSON.getAsJsonObject(transactionData), height);
Expand All @@ -443,7 +483,6 @@ static Block parseBlock(JsonObject blockData, int height)
"Block contains duplicate transactions: " + transaction.getStringId());
}
}

byte[] blockAts = Convert.parseHexString(JSON.getAsString(blockData.get("blockATs")));
return new Block(
version,
Expand Down Expand Up @@ -473,6 +512,28 @@ static Block parseBlock(JsonObject blockData, int height)
}

public byte[] getBytes() {
if (cachedBytes.get() == null) {
synchronized (this) {
if (cachedBytes.get() == null) {
byte[] unsignedBytes = getUnsignedBytes();
ByteBuffer buffer = ByteBuffer.allocate(unsignedBytes.length + blockSignature.length);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.put(unsignedBytes);
if (buffer.limit() - buffer.position() < blockSignature.length) {
logger.error("Something is too large here "
+ "- buffer should have {} bytes left but only has {}",
blockSignature.length,
(buffer.limit() - buffer.position()));
}
buffer.put(blockSignature);
cachedBytes.set(buffer.array());
}
}
}
return cachedBytes.get();
}

byte[] getUnsignedBytes() {
ByteBuffer buffer = ByteBuffer.allocate(
4
+ 4
Expand All @@ -484,8 +545,7 @@ public byte[] getBytes() {
+ 32
+ (32 + 32)
+ 8
+ (blockAts != null ? blockAts.length : 0)
+ 64);
+ (blockAts != null ? blockAts.length : 0));
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(version);
buffer.putInt(timestamp);
Expand All @@ -509,25 +569,35 @@ public byte[] getBytes() {
if (blockAts != null) {
buffer.put(blockAts);
}
if (buffer.limit() - buffer.position() < blockSignature.length) {
logger.error("Something is too large here "
+ "- buffer should have {} bytes left but only has {}",
blockSignature.length,
(buffer.limit() - buffer.position()));
}
buffer.put(blockSignature);
return buffer.array();
}

void sign(String secretPhrase) {
if (blockSignature != null) {
throw new IllegalStateException("Block already signed");
synchronized (this) {
if (blockSignature != null) {
throw new IllegalStateException("Block already signed");
}
// 1. Calculate the unsigned bytes first.
byte[] unsignedBytes = getUnsignedBytes();

// 2. Sign the unsigned bytes to get the block signature.
blockSignature = Crypto.sign(unsignedBytes, secretPhrase);

// 3. Now that blockSignature is available, construct the full signed bytes
// and cache them. This ensures cachedBytes always holds the final, signed
// state.
ByteBuffer buffer = ByteBuffer.allocate(unsignedBytes.length + blockSignature.length);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.put(unsignedBytes);
if (buffer.limit() - buffer.position() < blockSignature.length) {
logger.error("Something is too large here "
+ "- buffer should have {} bytes left but only has {}",
blockSignature.length,
(buffer.limit() - buffer.position()));
}
buffer.put(blockSignature);
cachedBytes.set(buffer.array()); // Cache the final, signed bytes
}
blockSignature = new byte[64];
byte[] data = getBytes();
byte[] data2 = new byte[data.length - 64];
System.arraycopy(data, 0, data2, 0, data2.length);
blockSignature = Crypto.sign(data2, secretPhrase);
}

public byte[] getBlockAts() {
Expand Down
Loading