diff --git a/src/main/java/org/qortal/account/Account.java b/src/main/java/org/qortal/account/Account.java index e2b96bd6..654698e2 100644 --- a/src/main/java/org/qortal/account/Account.java +++ b/src/main/java/org/qortal/account/Account.java @@ -3,6 +3,9 @@ package org.qortal.account; import java.math.BigDecimal; import java.util.List; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.qortal.block.BlockChain; @@ -15,6 +18,7 @@ import org.qortal.repository.Repository; import org.qortal.transaction.Transaction; import org.qortal.utils.Base58; +@XmlAccessorType(XmlAccessType.NONE) // Stops JAX-RS errors when unmarshalling blockchain config public class Account { private static final Logger LOGGER = LogManager.getLogger(Account.class); diff --git a/src/main/java/org/qortal/account/GenesisAccount.java b/src/main/java/org/qortal/account/GenesisAccount.java deleted file mode 100644 index 5077322d..00000000 --- a/src/main/java/org/qortal/account/GenesisAccount.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.qortal.account; - -import org.qortal.repository.Repository; - -public final class GenesisAccount extends PublicKeyAccount { - - public static final byte[] PUBLIC_KEY = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - - public GenesisAccount(Repository repository) { - super(repository, PUBLIC_KEY); - } - -} diff --git a/src/main/java/org/qortal/account/NullAccount.java b/src/main/java/org/qortal/account/NullAccount.java new file mode 100644 index 00000000..1360d940 --- /dev/null +++ b/src/main/java/org/qortal/account/NullAccount.java @@ -0,0 +1,24 @@ +package org.qortal.account; + +import org.qortal.crypto.Crypto; +import org.qortal.repository.Repository; + +public final class NullAccount extends PublicKeyAccount { + + public static final byte[] PUBLIC_KEY = new byte[32]; + public static final String ADDRESS = Crypto.toAddress(PUBLIC_KEY); + + public NullAccount(Repository repository) { + super(repository, PUBLIC_KEY, ADDRESS); + } + + protected NullAccount() { + } + + @Override + public boolean verify(byte[] signature, byte[] message) { + // Can't sign, hence can't verify. + return false; + } + +} diff --git a/src/main/java/org/qortal/account/PublicKeyAccount.java b/src/main/java/org/qortal/account/PublicKeyAccount.java index 63739704..cfae0bb9 100644 --- a/src/main/java/org/qortal/account/PublicKeyAccount.java +++ b/src/main/java/org/qortal/account/PublicKeyAccount.java @@ -22,6 +22,18 @@ public class PublicKeyAccount extends Account { this.publicKey = edPublicKeyParams.getEncoded(); } + protected PublicKeyAccount(Repository repository, byte[] publicKey, String address) { + super(repository, address); + + this.publicKey = publicKey; + this.edPublicKeyParams = null; + } + + protected PublicKeyAccount() { + this.publicKey = null; + this.edPublicKeyParams = null; + } + public byte[] getPublicKey() { return this.publicKey; } diff --git a/src/main/java/org/qortal/at/QortalATAPI.java b/src/main/java/org/qortal/at/QortalATAPI.java index 8995c506..ff6765c3 100644 --- a/src/main/java/org/qortal/at/QortalATAPI.java +++ b/src/main/java/org/qortal/at/QortalATAPI.java @@ -14,7 +14,7 @@ import org.ciyam.at.MachineState; import org.ciyam.at.OpCode; import org.ciyam.at.Timestamp; import org.qortal.account.Account; -import org.qortal.account.GenesisAccount; +import org.qortal.account.NullAccount; import org.qortal.account.PublicKeyAccount; import org.qortal.asset.Asset; import org.qortal.crypto.Crypto; @@ -270,7 +270,7 @@ public class QortalATAPI extends API { byte[] reference = this.getLastReference(); BigDecimal amount = BigDecimal.valueOf(unscaledAmount, 8); - BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, GenesisAccount.PUBLIC_KEY, BigDecimal.ZERO, null); + BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, NullAccount.PUBLIC_KEY, BigDecimal.ZERO, null); ATTransactionData atTransactionData = new ATTransactionData(baseTransactionData, this.atData.getATAddress(), recipient.getAddress(), amount, this.atData.getAssetId(), new byte[0]); AtTransaction atTransaction = new AtTransaction(this.repository, atTransactionData); @@ -289,7 +289,7 @@ public class QortalATAPI extends API { long timestamp = this.getNextTransactionTimestamp(); byte[] reference = this.getLastReference(); - BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, GenesisAccount.PUBLIC_KEY, BigDecimal.ZERO, null); + BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, NullAccount.PUBLIC_KEY, BigDecimal.ZERO, null); ATTransactionData atTransactionData = new ATTransactionData(baseTransactionData, this.atData.getATAddress(), recipient.getAddress(), BigDecimal.ZERO, this.atData.getAssetId(), message); AtTransaction atTransaction = new AtTransaction(this.repository, atTransactionData); @@ -316,7 +316,7 @@ public class QortalATAPI extends API { byte[] reference = this.getLastReference(); BigDecimal amount = BigDecimal.valueOf(finalBalance, 8); - BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, GenesisAccount.PUBLIC_KEY, BigDecimal.ZERO, null); + BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, NullAccount.PUBLIC_KEY, BigDecimal.ZERO, null); ATTransactionData atTransactionData = new ATTransactionData(baseTransactionData, this.atData.getATAddress(), creator.getAddress(), amount, this.atData.getAssetId(), new byte[0]); AtTransaction atTransaction = new AtTransaction(this.repository, atTransactionData); diff --git a/src/main/java/org/qortal/block/GenesisBlock.java b/src/main/java/org/qortal/block/GenesisBlock.java index 94cb64e7..610528d2 100644 --- a/src/main/java/org/qortal/block/GenesisBlock.java +++ b/src/main/java/org/qortal/block/GenesisBlock.java @@ -7,27 +7,21 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.qortal.account.Account; -import org.qortal.account.GenesisAccount; -import org.qortal.account.PublicKeyAccount; +import org.qortal.account.NullAccount; import org.qortal.crypto.Crypto; import org.qortal.data.asset.AssetData; import org.qortal.data.block.BlockData; -import org.qortal.data.transaction.IssueAssetTransactionData; import org.qortal.data.transaction.TransactionData; -import org.qortal.group.Group; import org.qortal.repository.DataException; import org.qortal.repository.Repository; import org.qortal.transaction.Transaction; import org.qortal.transaction.Transaction.ApprovalStatus; -import org.qortal.transaction.Transaction.TransactionType; import org.qortal.transform.TransformationException; import org.qortal.transform.transaction.TransactionTransformer; @@ -39,9 +33,8 @@ public class GenesisBlock extends Block { private static final Logger LOGGER = LogManager.getLogger(GenesisBlock.class); - private static final byte[] GENESIS_REFERENCE = new byte[] { - 1, 1, 1, 1, 1, 1, 1, 1 - }; // NOTE: Neither 64 nor 128 bytes! + private static final byte[] GENESIS_BLOCK_REFERENCE = new byte[128]; + private static final byte[] GENESIS_TRANSACTION_REFERENCE = new byte[64]; @XmlAccessorType(XmlAccessType.FIELD) public static class GenesisInfo { @@ -96,37 +89,16 @@ public class GenesisBlock extends Block { transactionData.setFee(BigDecimal.ZERO.setScale(8)); if (transactionData.getCreatorPublicKey() == null) - transactionData.setCreatorPublicKey(GenesisAccount.PUBLIC_KEY); + transactionData.setCreatorPublicKey(NullAccount.PUBLIC_KEY); if (transactionData.getTimestamp() == 0) transactionData.setTimestamp(info.timestamp); }); - // For version 1, extract any ISSUE_ASSET transactions into initialAssets and only allow GENESIS transactions - if (info.version == 1) { - List issueAssetTransactions = transactionsData.stream() - .filter(transactionData -> transactionData.getType() == TransactionType.ISSUE_ASSET).collect(Collectors.toList()); - transactionsData.removeAll(issueAssetTransactions); - - // There should be only GENESIS transactions left; - if (transactionsData.stream().anyMatch(transactionData -> transactionData.getType() != TransactionType.GENESIS)) { - LOGGER.error("Version 1 genesis block only allowed to contain GENESIS transctions (after issue-asset processing)"); - throw new RuntimeException("Version 1 genesis block only allowed to contain GENESIS transctions (after issue-asset processing)"); - } - - // Convert ISSUE_ASSET transactions into initial assets - initialAssets = issueAssetTransactions.stream().map(transactionData -> { - IssueAssetTransactionData issueAssetTransactionData = (IssueAssetTransactionData) transactionData; - - return new AssetData(issueAssetTransactionData.getOwner(), issueAssetTransactionData.getAssetName(), issueAssetTransactionData.getDescription(), - issueAssetTransactionData.getQuantity(), issueAssetTransactionData.getIsDivisible(), "", false, Group.NO_GROUP, issueAssetTransactionData.getReference()); - }).collect(Collectors.toList()); - } - - byte[] reference = GENESIS_REFERENCE; + byte[] reference = GENESIS_BLOCK_REFERENCE; int transactionCount = transactionsData.size(); BigDecimal totalFees = BigDecimal.ZERO.setScale(8); - byte[] minterPublicKey = GenesisAccount.PUBLIC_KEY; + byte[] minterPublicKey = NullAccount.PUBLIC_KEY; byte[] bytesForSignature = getBytesForMinterSignature(info.timestamp, reference, minterPublicKey); byte[] minterSignature = calcGenesisMinterSignature(bytesForSignature); byte[] transactionsSignature = calcGenesisTransactionsSignature(); @@ -212,24 +184,16 @@ public class GenesisBlock extends Block { try { // Passing expected size to ByteArrayOutputStream avoids reallocation when adding more bytes than default 32. // See below for explanation of some of the values used to calculated expected size. - ByteArrayOutputStream bytes = new ByteArrayOutputStream(8 + 64 + 8 + 32); + ByteArrayOutputStream bytes = new ByteArrayOutputStream(8 + 128 + 32); - /* - * NOTE: Historic code had genesis block using Longs.toByteArray(version) compared to standard block's Ints.toByteArray. The subsequent - * Bytes.ensureCapacity(versionBytes, 0, 4) did not truncate versionBytes back to 4 bytes either. This means 8 bytes were used even though - * VERSION_LENGTH is set to 4. Correcting this historic bug will break genesis block signatures! - */ - // For Qortal, we use genesis timestamp instead + // Genesis block timestamp bytes.write(Longs.toByteArray(timestamp)); - /* - * NOTE: Historic code had the reference expanded to only 64 bytes whereas standard block references are 128 bytes. Correcting this historic bug - * will break genesis block signatures! - */ - bytes.write(Bytes.ensureCapacity(reference, 64, 0)); + // Block's reference + bytes.write(reference); - // NOTE: Genesis account's public key is only 8 bytes, not the usual 32, so we have to pad. - bytes.write(Bytes.ensureCapacity(minterPublicKey, 32, 0)); + // Minting account's public key (typically NullAccount) + bytes.write(minterPublicKey); return bytes.toByteArray(); } catch (IOException e) { @@ -295,26 +259,18 @@ public class GenesisBlock extends Block { public void process() throws DataException { LOGGER.info(String.format("Using genesis block timestamp of %d", this.blockData.getTimestamp())); - // If we're a version 1 genesis block, create assets now - if (this.blockData.getVersion() == 1) - for (AssetData assetData : initialAssets) - repository.getAssetRepository().save(assetData); - /* * Some transactions will be missing references and signatures, - * so we generate them by trial-processing transactions and using - * account's last-reference to fill in the gaps for reference, + * so we generate them by using GENESIS_TRANSACTION_REFERENCE * and a duplicated SHA256 digest for signature */ - this.repository.setSavepoint(); try { for (Transaction transaction : this.getTransactions()) { TransactionData transactionData = transaction.getTransactionData(); - Account creator = new PublicKeyAccount(this.repository, transactionData.getCreatorPublicKey()); // Missing reference? if (transactionData.getReference() == null) - transactionData.setReference(creator.getLastReference()); + transactionData.setReference(GENESIS_TRANSACTION_REFERENCE); // Missing signature? if (transactionData.getSignature() == null) { @@ -324,18 +280,11 @@ public class GenesisBlock extends Block { transactionData.setSignature(signature); } - // Missing approval status (not used in V1) + // Approval status transactionData.setApprovalStatus(ApprovalStatus.NOT_REQUIRED); - - // Ask transaction to update references, etc. - transaction.processReferencesAndFees(); - - creator.setLastReference(transactionData.getSignature()); } } catch (TransformationException e) { throw new RuntimeException("Can't process genesis block transaction", e); - } finally { - this.repository.rollbackToSavepoint(); } // Save transactions into repository ready for processing diff --git a/src/main/java/org/qortal/data/transaction/ATTransactionData.java b/src/main/java/org/qortal/data/transaction/ATTransactionData.java index c2e81886..10429ba0 100644 --- a/src/main/java/org/qortal/data/transaction/ATTransactionData.java +++ b/src/main/java/org/qortal/data/transaction/ATTransactionData.java @@ -6,7 +6,7 @@ import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; -import org.qortal.account.GenesisAccount; +import org.qortal.account.NullAccount; import org.qortal.transaction.Transaction.TransactionType; import io.swagger.v3.oas.annotations.media.Schema; @@ -31,14 +31,14 @@ public class ATTransactionData extends TransactionData { } public void afterUnmarshal(Unmarshaller u, Object parent) { - this.creatorPublicKey = GenesisAccount.PUBLIC_KEY; + this.creatorPublicKey = NullAccount.PUBLIC_KEY; } /** From repository */ public ATTransactionData(BaseTransactionData baseTransactionData, String atAddress, String recipient, BigDecimal amount, Long assetId, byte[] message) { super(TransactionType.AT, baseTransactionData); - this.creatorPublicKey = GenesisAccount.PUBLIC_KEY; + this.creatorPublicKey = NullAccount.PUBLIC_KEY; this.atAddress = atAddress; this.recipient = recipient; this.amount = amount; diff --git a/src/main/java/org/qortal/data/transaction/AccountFlagsTransactionData.java b/src/main/java/org/qortal/data/transaction/AccountFlagsTransactionData.java index 994cb840..f5f29e1b 100644 --- a/src/main/java/org/qortal/data/transaction/AccountFlagsTransactionData.java +++ b/src/main/java/org/qortal/data/transaction/AccountFlagsTransactionData.java @@ -6,7 +6,7 @@ import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; -import org.qortal.account.GenesisAccount; +import org.qortal.account.NullAccount; import org.qortal.block.GenesisBlock; import org.qortal.transaction.Transaction.TransactionType; @@ -36,10 +36,10 @@ public class AccountFlagsTransactionData extends TransactionData { /* * If we're being constructed as part of the genesis block info inside blockchain config * and no specific creator's public key is supplied - * then use genesis account's public key. + * then use null account's public key. */ if (parent instanceof GenesisBlock.GenesisInfo && this.creatorPublicKey == null) - this.creatorPublicKey = GenesisAccount.PUBLIC_KEY; + this.creatorPublicKey = NullAccount.PUBLIC_KEY; } /** From repository */ diff --git a/src/main/java/org/qortal/data/transaction/AccountLevelTransactionData.java b/src/main/java/org/qortal/data/transaction/AccountLevelTransactionData.java index 3385ca8c..4b55e375 100644 --- a/src/main/java/org/qortal/data/transaction/AccountLevelTransactionData.java +++ b/src/main/java/org/qortal/data/transaction/AccountLevelTransactionData.java @@ -6,7 +6,7 @@ import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; -import org.qortal.account.GenesisAccount; +import org.qortal.account.NullAccount; import org.qortal.block.GenesisBlock; import org.qortal.transaction.Transaction.TransactionType; @@ -33,10 +33,10 @@ public class AccountLevelTransactionData extends TransactionData { /* * If we're being constructed as part of the genesis block info inside blockchain config * and no specific creator's public key is supplied - * then use genesis account's public key. + * then use null account's public key. */ if (parent instanceof GenesisBlock.GenesisInfo && this.creatorPublicKey == null) - this.creatorPublicKey = GenesisAccount.PUBLIC_KEY; + this.creatorPublicKey = NullAccount.PUBLIC_KEY; } /** From repository, network/API */ diff --git a/src/main/java/org/qortal/data/transaction/GenesisTransactionData.java b/src/main/java/org/qortal/data/transaction/GenesisTransactionData.java index 3e644a70..c92ed605 100644 --- a/src/main/java/org/qortal/data/transaction/GenesisTransactionData.java +++ b/src/main/java/org/qortal/data/transaction/GenesisTransactionData.java @@ -34,10 +34,8 @@ public class GenesisTransactionData extends TransactionData { super(TransactionType.GENESIS); } - /** From repository (V2) */ + /** From repository */ public GenesisTransactionData(BaseTransactionData baseTransactionData, String recipient, BigDecimal amount, long assetId) { - // No groupID, null reference, zero fee, no approval required, height always 1 - // super(TransactionType.GENESIS, timestamp, Group.NO_GROUP, null, GenesisAccount.PUBLIC_KEY, BigDecimal.ZERO, ApprovalStatus.NOT_REQUIRED, 1, signature); super(TransactionType.GENESIS, baseTransactionData); this.recipient = recipient; @@ -45,7 +43,7 @@ public class GenesisTransactionData extends TransactionData { this.assetId = assetId; } - /** From repository (V1, where asset locked to QORT) */ + /** From repository (where asset locked to QORT) */ public GenesisTransactionData(BaseTransactionData baseTransactionData, String recipient, BigDecimal amount) { this(baseTransactionData, recipient, amount, Asset.QORT); } diff --git a/src/main/java/org/qortal/data/transaction/IssueAssetTransactionData.java b/src/main/java/org/qortal/data/transaction/IssueAssetTransactionData.java index c2b06f0f..da23e5f1 100644 --- a/src/main/java/org/qortal/data/transaction/IssueAssetTransactionData.java +++ b/src/main/java/org/qortal/data/transaction/IssueAssetTransactionData.java @@ -5,7 +5,7 @@ import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; -import org.qortal.account.GenesisAccount; +import org.qortal.account.NullAccount; import org.qortal.block.GenesisBlock; import org.qortal.transaction.Transaction.TransactionType; @@ -51,10 +51,10 @@ public class IssueAssetTransactionData extends TransactionData { /* * If we're being constructed as part of the genesis block info inside blockchain config * and no specific issuer's public key is supplied - * then use genesis account's public key. + * then use null account's public key. */ if (parent instanceof GenesisBlock.GenesisInfo && this.issuerPublicKey == null) - this.issuerPublicKey = GenesisAccount.PUBLIC_KEY; + this.issuerPublicKey = NullAccount.PUBLIC_KEY; this.creatorPublicKey = this.issuerPublicKey; } diff --git a/src/main/java/org/qortal/transaction/AccountFlagsTransaction.java b/src/main/java/org/qortal/transaction/AccountFlagsTransaction.java index a795b848..bc7511d5 100644 --- a/src/main/java/org/qortal/transaction/AccountFlagsTransaction.java +++ b/src/main/java/org/qortal/transaction/AccountFlagsTransaction.java @@ -5,7 +5,7 @@ import java.util.Collections; import java.util.List; import org.qortal.account.Account; -import org.qortal.account.GenesisAccount; +import org.qortal.account.NullAccount; import org.qortal.asset.Asset; import org.qortal.data.transaction.AccountFlagsTransactionData; import org.qortal.data.transaction.TransactionData; @@ -68,8 +68,8 @@ public class AccountFlagsTransaction extends Transaction { public ValidationResult isValid() throws DataException { Account creator = getCreator(); - // Only genesis account can modify flags - if (!creator.getAddress().equals(new GenesisAccount(repository).getAddress())) + // Only null account can modify flags + if (!creator.getAddress().equals(NullAccount.ADDRESS)) return ValidationResult.NO_FLAG_PERMISSION; // Check fee is zero or positive diff --git a/src/main/java/org/qortal/transaction/AccountLevelTransaction.java b/src/main/java/org/qortal/transaction/AccountLevelTransaction.java index 63f234e0..14d46f29 100644 --- a/src/main/java/org/qortal/transaction/AccountLevelTransaction.java +++ b/src/main/java/org/qortal/transaction/AccountLevelTransaction.java @@ -5,7 +5,7 @@ import java.util.Collections; import java.util.List; import org.qortal.account.Account; -import org.qortal.account.GenesisAccount; +import org.qortal.account.NullAccount; import org.qortal.asset.Asset; import org.qortal.block.BlockChain; import org.qortal.data.transaction.AccountLevelTransactionData; @@ -70,7 +70,7 @@ public class AccountLevelTransaction extends Transaction { Account creator = getCreator(); // Only genesis account can modify level - if (!creator.getAddress().equals(new GenesisAccount(repository).getAddress())) + if (!creator.getAddress().equals(new NullAccount(repository).getAddress())) return ValidationResult.NO_FLAG_PERMISSION; // Check fee is zero or positive diff --git a/src/main/java/org/qortal/transform/transaction/GenesisTransactionTransformer.java b/src/main/java/org/qortal/transform/transaction/GenesisTransactionTransformer.java index 1f8cf918..4527d186 100644 --- a/src/main/java/org/qortal/transform/transaction/GenesisTransactionTransformer.java +++ b/src/main/java/org/qortal/transform/transaction/GenesisTransactionTransformer.java @@ -5,7 +5,7 @@ import java.io.IOException; import java.math.BigDecimal; import java.nio.ByteBuffer; -import org.qortal.account.GenesisAccount; +import org.qortal.account.NullAccount; import org.qortal.data.transaction.BaseTransactionData; import org.qortal.data.transaction.GenesisTransactionData; import org.qortal.data.transaction.TransactionData; @@ -49,7 +49,7 @@ public class GenesisTransactionTransformer extends TransactionTransformer { long assetId = byteBuffer.getLong(); - BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, null, GenesisAccount.PUBLIC_KEY, BigDecimal.ZERO, null); + BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, null, NullAccount.PUBLIC_KEY, BigDecimal.ZERO, null); return new GenesisTransactionData(baseTransactionData, recipient, amount, assetId); }