forked from Qortal/qortal
Add Qortal AT FunctionCodes for getting account level / blocks minted + tests
This commit is contained in:
parent
d9de27e6f2
commit
eb9b94b9c6
@ -205,6 +205,12 @@ public class Account {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns account's blockMinted (0+) or null if account not found in repository. */
|
||||
public Integer getBlocksMinted() throws DataException {
|
||||
return this.repository.getAccountRepository().getMintedBlockCount(this.address);
|
||||
}
|
||||
|
||||
|
||||
/** Returns whether account can build reward-shares.
|
||||
* <p>
|
||||
* To be able to create reward-shares, the account needs to pass at least one of these tests:<br>
|
||||
|
@ -551,7 +551,7 @@ public class QortalATAPI extends API {
|
||||
* <p>
|
||||
* Otherwise, assume B is a public key.
|
||||
*/
|
||||
private Account getAccountFromB(MachineState state) {
|
||||
/*package*/ Account getAccountFromB(MachineState state) {
|
||||
byte[] bBytes = this.getB(state);
|
||||
|
||||
if ((bBytes[0] == Crypto.ADDRESS_VERSION || bBytes[0] == Crypto.AT_ADDRESS_VERSION)
|
||||
|
@ -10,9 +10,11 @@ import org.ciyam.at.ExecutionException;
|
||||
import org.ciyam.at.FunctionData;
|
||||
import org.ciyam.at.IllegalFunctionCodeException;
|
||||
import org.ciyam.at.MachineState;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.crosschain.Bitcoin;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.settings.Settings;
|
||||
|
||||
/**
|
||||
@ -160,6 +162,68 @@ public enum QortalFunctionCode {
|
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
|
||||
convertAddressInB(Crypto.ADDRESS_VERSION, state);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Returns account level of account in B.<br>
|
||||
* <tt>0x0520</tt><br>
|
||||
* B should contain either Qortal address or public key,<br>
|
||||
* e.g. as a result of calling function {@link org.ciyam.at.FunctionCode#PUT_ADDRESS_FROM_TX_IN_A_INTO_B}</code>.
|
||||
* <p></p>
|
||||
* Returns account level, or -1 if account unknown.
|
||||
* <p></p>
|
||||
* @see QortalATAPI#getAccountFromB(MachineState)
|
||||
*/
|
||||
GET_ACCOUNT_LEVEL_FROM_ACCOUNT_IN_B(0x0520, 0, true) {
|
||||
@Override
|
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
|
||||
QortalATAPI api = (QortalATAPI) state.getAPI();
|
||||
Account account = api.getAccountFromB(state);
|
||||
|
||||
Integer accountLevel = null;
|
||||
|
||||
if (account != null) {
|
||||
try {
|
||||
accountLevel = account.getLevel();
|
||||
} catch (DataException e) {
|
||||
throw new RuntimeException("AT API unable to fetch account level?", e);
|
||||
}
|
||||
}
|
||||
|
||||
functionData.returnValue = accountLevel != null
|
||||
? accountLevel.longValue()
|
||||
: -1;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Returns account's minted block count of account in B.<br>
|
||||
* <tt>0x0521</tt><br>
|
||||
* B should contain either Qortal address or public key,<br>
|
||||
* e.g. as a result of calling function {@link org.ciyam.at.FunctionCode#PUT_ADDRESS_FROM_TX_IN_A_INTO_B}</code>.
|
||||
* <p></p>
|
||||
* Returns account level, or -1 if account unknown.
|
||||
* <p></p>
|
||||
* @see QortalATAPI#getAccountFromB(MachineState)
|
||||
*/
|
||||
GET_BLOCKS_MINTED_FROM_ACCOUNT_IN_B(0x0521, 0, true) {
|
||||
@Override
|
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
|
||||
QortalATAPI api = (QortalATAPI) state.getAPI();
|
||||
Account account = api.getAccountFromB(state);
|
||||
|
||||
Integer blocksMinted = null;
|
||||
|
||||
if (account != null) {
|
||||
try {
|
||||
blocksMinted = account.getBlocksMinted();
|
||||
} catch (DataException e) {
|
||||
throw new RuntimeException("AT API unable to fetch account's minted block count?", e);
|
||||
}
|
||||
}
|
||||
|
||||
functionData.returnValue = blocksMinted != null
|
||||
? blocksMinted.longValue()
|
||||
: -1;
|
||||
}
|
||||
};
|
||||
|
||||
public final short value;
|
||||
|
@ -76,6 +76,9 @@ public interface AccountRepository {
|
||||
*/
|
||||
public void setBlocksMintedAdjustment(AccountData accountData) throws DataException;
|
||||
|
||||
/** Returns account's minted block count or null if account not found. */
|
||||
public Integer getMintedBlockCount(String address) throws DataException;
|
||||
|
||||
/**
|
||||
* Saves account's minted block count and public key if present, in repository.
|
||||
* <p>
|
||||
|
@ -241,6 +241,20 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getMintedBlockCount(String address) throws DataException {
|
||||
String sql = "SELECT blocks_minted FROM Accounts WHERE account = ?";
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, address)) {
|
||||
if (resultSet == null)
|
||||
return null;
|
||||
|
||||
return resultSet.getInt(1);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch account's minted block count from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMintedBlockCount(AccountData accountData) throws DataException {
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("Accounts");
|
||||
|
@ -0,0 +1,186 @@
|
||||
package org.qortal.test.at.qortalfunctioncodes;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import org.ciyam.at.CompilationException;
|
||||
import org.ciyam.at.FunctionCode;
|
||||
import org.ciyam.at.MachineState;
|
||||
import org.ciyam.at.OpCode;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.at.QortalFunctionCode;
|
||||
import org.qortal.data.at.ATStateData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.AtUtils;
|
||||
import org.qortal.test.common.BlockUtils;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.TestAccount;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.BitTwiddling;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class GetAccountBlocksMintedTests extends Common {
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
private static final long fundingAmount = 1_00000000L;
|
||||
|
||||
private Repository repository = null;
|
||||
private byte[] creationBytes = null;
|
||||
private PrivateKeyAccount deployer;
|
||||
private DeployAtTransaction deployAtTransaction;
|
||||
private String atAddress;
|
||||
|
||||
@Before
|
||||
public void before() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
|
||||
this.repository = RepositoryManager.getRepository();
|
||||
this.deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() throws DataException {
|
||||
if (this.repository != null)
|
||||
this.repository.close();
|
||||
|
||||
this.repository = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAccountBlocksMintedFromAddress() throws DataException {
|
||||
Account alice = Common.getTestAccount(repository, "alice");
|
||||
byte[] accountBytes = Bytes.ensureCapacity(Base58.decode(alice.getAddress()), 32, 0);
|
||||
|
||||
this.creationBytes = buildGetAccountBlocksMintedAT(accountBytes);
|
||||
|
||||
this.deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
this.atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a block to allow AT to run - Alice's blocksMinted is incremented AFTER block is processed / AT is run
|
||||
Integer expectedBlocksMinted = alice.getBlocksMinted();
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
Integer extractedBlocksMinted = extractBlocksMinted(repository, atAddress);
|
||||
assertEquals(expectedBlocksMinted, extractedBlocksMinted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAccountBlocksMintedFromPublicKey() throws DataException {
|
||||
TestAccount alice = Common.getTestAccount(repository, "alice");
|
||||
byte[] accountBytes = alice.getPublicKey();
|
||||
|
||||
this.creationBytes = buildGetAccountBlocksMintedAT(accountBytes);
|
||||
|
||||
this.deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
this.atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a block to allow AT to run - Alice's blocksMinted is incremented AFTER block is processed / AT is run
|
||||
Integer expectedBlocksMinted = alice.getBlocksMinted();
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
Integer extractedBlocksMinted = extractBlocksMinted(repository, atAddress);
|
||||
assertEquals(expectedBlocksMinted, extractedBlocksMinted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnknownAccountBlocksMinted() throws DataException {
|
||||
byte[] accountBytes = new byte[32];
|
||||
RANDOM.nextBytes(accountBytes);
|
||||
|
||||
this.creationBytes = buildGetAccountBlocksMintedAT(accountBytes);
|
||||
|
||||
this.deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
this.atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a block to allow AT to run - Alice's blocksMinted is incremented AFTER block is processed / AT is run
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
Integer extractedBlocksMinted = extractBlocksMinted(repository, atAddress);
|
||||
assertNull(extractedBlocksMinted);
|
||||
}
|
||||
|
||||
private static byte[] buildGetAccountBlocksMintedAT(byte[] accountBytes) {
|
||||
// Labels for data segment addresses
|
||||
int addrCounter = 0;
|
||||
|
||||
// Beginning of data segment for easy extraction
|
||||
final int addrBlocksMinted = addrCounter++;
|
||||
|
||||
// accountBytes
|
||||
final int addrAccountBytes = addrCounter;
|
||||
addrCounter += 4;
|
||||
|
||||
// Pointer to accountBytes so we can load them into B
|
||||
final int addrAccountBytesPointer = addrCounter++;
|
||||
|
||||
// Data segment
|
||||
ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE);
|
||||
|
||||
// Write accountBytes
|
||||
dataByteBuffer.position(addrAccountBytes * MachineState.VALUE_SIZE);
|
||||
dataByteBuffer.put(accountBytes);
|
||||
|
||||
// Store pointer to addrAccountbytes at addrAccountBytesPointer
|
||||
assertEquals(addrAccountBytesPointer * MachineState.VALUE_SIZE, dataByteBuffer.position());
|
||||
dataByteBuffer.putLong(addrAccountBytes);
|
||||
|
||||
ByteBuffer codeByteBuffer = ByteBuffer.allocate(512);
|
||||
|
||||
// Two-pass version
|
||||
for (int pass = 0; pass < 2; ++pass) {
|
||||
codeByteBuffer.clear();
|
||||
|
||||
try {
|
||||
/* Initialization */
|
||||
|
||||
// Copy accountBytes from data segment into B, starting at addrAccountBytes (as pointed to by addrAccountBytesPointer)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrAccountBytesPointer));
|
||||
|
||||
// Get account's blocks minted count and save into addrBlocksMinted
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(QortalFunctionCode.GET_BLOCKS_MINTED_FROM_ACCOUNT_IN_B.value, addrBlocksMinted));
|
||||
|
||||
// We're done
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.compile());
|
||||
} catch (CompilationException e) {
|
||||
throw new IllegalStateException("Unable to compile AT?", e);
|
||||
}
|
||||
}
|
||||
|
||||
codeByteBuffer.flip();
|
||||
|
||||
byte[] codeBytes = new byte[codeByteBuffer.limit()];
|
||||
codeByteBuffer.get(codeBytes);
|
||||
|
||||
final short ciyamAtVersion = 2;
|
||||
final short numCallStackPages = 0;
|
||||
final short numUserStackPages = 0;
|
||||
final long minActivationAmount = 0L;
|
||||
|
||||
return MachineState.toCreationBytes(ciyamAtVersion, codeBytes, dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount);
|
||||
}
|
||||
|
||||
private Integer extractBlocksMinted(Repository repository, String atAddress) throws DataException {
|
||||
// Check AT result
|
||||
ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
byte[] stateData = atStateData.getStateData();
|
||||
|
||||
byte[] dataBytes = MachineState.extractDataBytes(stateData);
|
||||
|
||||
Long blocksMintedValue = BitTwiddling.longFromBEBytes(dataBytes, 0);
|
||||
if (blocksMintedValue == -1)
|
||||
return null;
|
||||
|
||||
return blocksMintedValue.intValue();
|
||||
}
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
package org.qortal.test.at.qortalfunctioncodes;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import org.ciyam.at.CompilationException;
|
||||
import org.ciyam.at.FunctionCode;
|
||||
import org.ciyam.at.MachineState;
|
||||
import org.ciyam.at.OpCode;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.at.QortalFunctionCode;
|
||||
import org.qortal.data.at.ATStateData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.AtUtils;
|
||||
import org.qortal.test.common.BlockUtils;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.TestAccount;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.BitTwiddling;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class GetAccountLevelTests extends Common {
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
private static final long fundingAmount = 1_00000000L;
|
||||
|
||||
private Repository repository = null;
|
||||
private byte[] creationBytes = null;
|
||||
private PrivateKeyAccount deployer;
|
||||
private DeployAtTransaction deployAtTransaction;
|
||||
private String atAddress;
|
||||
|
||||
@Before
|
||||
public void before() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
|
||||
this.repository = RepositoryManager.getRepository();
|
||||
this.deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() throws DataException {
|
||||
if (this.repository != null)
|
||||
this.repository.close();
|
||||
|
||||
this.repository = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAccountLevelFromAddress() throws DataException {
|
||||
Account dilbert = Common.getTestAccount(repository, "dilbert");
|
||||
byte[] accountBytes = Bytes.ensureCapacity(Base58.decode(dilbert.getAddress()), 32, 0);
|
||||
|
||||
this.creationBytes = buildGetAccountLevelAT(accountBytes);
|
||||
|
||||
this.deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
this.atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a block to allow AT to run
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
Integer extractedAccountLevel = extractAccountLevel(repository, atAddress);
|
||||
assertEquals(dilbert.getLevel(), extractedAccountLevel);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAccountLevelFromPublicKey() throws DataException {
|
||||
TestAccount dilbert = Common.getTestAccount(repository, "dilbert");
|
||||
byte[] accountBytes = dilbert.getPublicKey();
|
||||
|
||||
this.creationBytes = buildGetAccountLevelAT(accountBytes);
|
||||
|
||||
this.deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
this.atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a block to allow AT to run
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
Integer extractedAccountLevel = extractAccountLevel(repository, atAddress);
|
||||
assertEquals(dilbert.getLevel(), extractedAccountLevel);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnknownAccountLevel() throws DataException {
|
||||
byte[] accountBytes = new byte[32];
|
||||
RANDOM.nextBytes(accountBytes);
|
||||
|
||||
this.creationBytes = buildGetAccountLevelAT(accountBytes);
|
||||
|
||||
this.deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
this.atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a block to allow AT to run
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
Integer extractedAccountLevel = extractAccountLevel(repository, atAddress);
|
||||
assertNull(extractedAccountLevel);
|
||||
}
|
||||
|
||||
private static byte[] buildGetAccountLevelAT(byte[] accountBytes) {
|
||||
// Labels for data segment addresses
|
||||
int addrCounter = 0;
|
||||
|
||||
// Beginning of data segment for easy extraction
|
||||
final int addrAccountLevel = addrCounter++;
|
||||
|
||||
// accountBytes
|
||||
final int addrAccountBytes = addrCounter;
|
||||
addrCounter += 4;
|
||||
|
||||
// Pointer to accountBytes so we can load them into B
|
||||
final int addrAccountBytesPointer = addrCounter++;
|
||||
|
||||
// Data segment
|
||||
ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE);
|
||||
|
||||
// Write accountBytes
|
||||
dataByteBuffer.position(addrAccountBytes * MachineState.VALUE_SIZE);
|
||||
dataByteBuffer.put(accountBytes);
|
||||
|
||||
// Store pointer to addrAccountbytes at addrAccountBytesPointer
|
||||
assertEquals(addrAccountBytesPointer * MachineState.VALUE_SIZE, dataByteBuffer.position());
|
||||
dataByteBuffer.putLong(addrAccountBytes);
|
||||
|
||||
ByteBuffer codeByteBuffer = ByteBuffer.allocate(512);
|
||||
|
||||
// Two-pass version
|
||||
for (int pass = 0; pass < 2; ++pass) {
|
||||
codeByteBuffer.clear();
|
||||
|
||||
try {
|
||||
/* Initialization */
|
||||
|
||||
// Copy accountBytes from data segment into B, starting at addrAccountBytes (as pointed to by addrAccountBytesPointer)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrAccountBytesPointer));
|
||||
|
||||
// Get account level and save into addrAccountLevel
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(QortalFunctionCode.GET_ACCOUNT_LEVEL_FROM_ACCOUNT_IN_B.value, addrAccountLevel));
|
||||
|
||||
// We're done
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.compile());
|
||||
} catch (CompilationException e) {
|
||||
throw new IllegalStateException("Unable to compile AT?", e);
|
||||
}
|
||||
}
|
||||
|
||||
codeByteBuffer.flip();
|
||||
|
||||
byte[] codeBytes = new byte[codeByteBuffer.limit()];
|
||||
codeByteBuffer.get(codeBytes);
|
||||
|
||||
final short ciyamAtVersion = 2;
|
||||
final short numCallStackPages = 0;
|
||||
final short numUserStackPages = 0;
|
||||
final long minActivationAmount = 0L;
|
||||
|
||||
return MachineState.toCreationBytes(ciyamAtVersion, codeBytes, dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount);
|
||||
}
|
||||
|
||||
private Integer extractAccountLevel(Repository repository, String atAddress) throws DataException {
|
||||
// Check AT result
|
||||
ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
byte[] stateData = atStateData.getStateData();
|
||||
|
||||
byte[] dataBytes = MachineState.extractDataBytes(stateData);
|
||||
|
||||
Long accountLevelValue = BitTwiddling.longFromBEBytes(dataBytes, 0);
|
||||
if (accountLevelValue == -1)
|
||||
return null;
|
||||
|
||||
return accountLevelValue.intValue();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user