mirror of
https://github.com/Qortal/AT.git
synced 2025-02-07 06:44:12 +00:00
API is now an abstract class instead of an Interface. This is to allow protected methods that give access to package-scoped methods and variables inside MachineState. MachineState now supports a different set of constants based on AT version. MachineState's methods and variables have had their scopes tightened up and getters/setters added where appropriate. MachineState.parseHeader() inlined into the constructor that calls it so it can set public final variables. Some reordering and additional comments in MachineState. OpCode enum entries refactored so that: a) variables provided via MachineState "state" are not explicitly passed as well b) args to each opcode are pre-fetched from the codeByteBuffer before calling and only need to be cast before use. this comes almost "for free" thanks to OpCodeParam.fetch Calling functions from OpCode now cleaner as there's no write-access to programCounter, only changing codeByteBuffer's position. Also in OpCode, state.getProgramCounter() now provides a consistent before-opcode position for branches, jumps, etc. Lots of repeated code refactored out of unit tests into ExecutableTest class. ExecutableTest also provides helper methods for examining post-execution data values, stack positions and entries.
216 lines
9.0 KiB
Java
216 lines
9.0 KiB
Java
import static common.TestUtils.hexToBytes;
|
|
import static org.junit.Assert.*;
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.security.MessageDigest;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.security.SecureRandom;
|
|
import java.security.Security;
|
|
import java.util.Arrays;
|
|
|
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
|
import org.ciyam.at.ExecutionException;
|
|
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.BeforeClass;
|
|
import org.junit.Test;
|
|
|
|
import common.ACCTAPI;
|
|
import common.TestLogger;
|
|
|
|
public class TestACCT {
|
|
|
|
public TestLogger logger;
|
|
public ACCTAPI api;
|
|
public MachineState state;
|
|
public ByteBuffer codeByteBuffer;
|
|
public ByteBuffer dataByteBuffer;
|
|
|
|
@BeforeClass
|
|
public static void beforeClass() {
|
|
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
|
}
|
|
|
|
@Before
|
|
public void beforeTest() {
|
|
logger = new TestLogger();
|
|
api = new ACCTAPI();
|
|
codeByteBuffer = ByteBuffer.allocate(0x0200 * 1).order(ByteOrder.LITTLE_ENDIAN);
|
|
dataByteBuffer = ByteBuffer.allocate(0x0020 * 8).order(ByteOrder.LITTLE_ENDIAN);
|
|
}
|
|
|
|
@After
|
|
public void afterTest() {
|
|
dataByteBuffer = null;
|
|
codeByteBuffer = null;
|
|
api = null;
|
|
logger = null;
|
|
}
|
|
|
|
private byte[] simulate() {
|
|
// version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4
|
|
byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000");
|
|
byte[] codeBytes = codeByteBuffer.array();
|
|
byte[] dataBytes = dataByteBuffer.array();
|
|
|
|
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
|
|
|
|
return executeAndCheck(state);
|
|
}
|
|
|
|
private byte[] continueSimulation(byte[] savedState) {
|
|
state = MachineState.fromBytes(api, logger, savedState);
|
|
|
|
return executeAndCheck(state);
|
|
}
|
|
|
|
private byte[] executeAndCheck(MachineState state) {
|
|
state.execute();
|
|
|
|
byte[] stateBytes = state.toBytes();
|
|
MachineState restoredState = MachineState.fromBytes(api, logger, stateBytes);
|
|
byte[] restoredStateBytes = restoredState.toBytes();
|
|
|
|
assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(stateBytes, restoredStateBytes));
|
|
|
|
return stateBytes;
|
|
}
|
|
|
|
@Test
|
|
public void testACCT() throws ExecutionException {
|
|
// DATA
|
|
final int addrHashPart1 = 0x0;
|
|
final int addrHashPart2 = 0x1;
|
|
final int addrHashPart3 = 0x2;
|
|
final int addrHashPart4 = 0x3;
|
|
final int addrAddressPart1 = 0x4;
|
|
final int addrAddressPart2 = 0x5;
|
|
final int addrAddressPart3 = 0x6;
|
|
final int addrAddressPart4 = 0x7;
|
|
final int addrRefundMinutes = 0x8;
|
|
final int addrRefundTimestamp = 0x9;
|
|
final int addrLastTimestamp = 0xa;
|
|
final int addrBlockTimestamp = 0xb;
|
|
final int addrTxType = 0xc;
|
|
final int addrComparator = 0xd;
|
|
final int addrAddressTemp1 = 0xe;
|
|
final int addrAddressTemp2 = 0xf;
|
|
final int addrAddressTemp3 = 0x10;
|
|
final int addrAddressTemp4 = 0x11;
|
|
|
|
byte[] secret = new byte[32];
|
|
new SecureRandom().nextBytes(secret);
|
|
|
|
try {
|
|
MessageDigest digester = MessageDigest.getInstance("SHA-256");
|
|
byte[] digest = digester.digest(secret);
|
|
|
|
dataByteBuffer.put(digest);
|
|
} catch (NoSuchAlgorithmException e) {
|
|
throw new ExecutionException("No SHA-256 message digest service available", e);
|
|
}
|
|
|
|
// Destination address (based on "R" for "Responder", where "R" is 0x52)
|
|
dataByteBuffer.put(hexToBytes("5200000000000000520000000000000052000000000000005200000000000000"));
|
|
|
|
// Expiry in minutes (but actually blocks in this test case)
|
|
dataByteBuffer.putLong(8L);
|
|
|
|
// Code labels
|
|
final int addrTxLoop = 0x36;
|
|
final int addrCheckTx = 0x4b;
|
|
final int addrCheckSender = 0x64;
|
|
final int addrCheckMessage = 0xab;
|
|
final int addrPayout = 0xdf;
|
|
final int addrRefund = 0x102;
|
|
|
|
int tempPC;
|
|
|
|
// init:
|
|
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_CREATION_TIMESTAMP.value).putInt(addrRefundTimestamp);
|
|
codeByteBuffer.put(OpCode.SET_DAT.value).putInt(addrLastTimestamp).putInt(addrRefundTimestamp);
|
|
codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort(FunctionCode.ADD_MINUTES_TO_TIMESTAMP.value).putInt(addrRefundTimestamp)
|
|
.putInt(addrRefundTimestamp).putInt(addrRefundMinutes);
|
|
codeByteBuffer.put(OpCode.SET_PCS.value);
|
|
|
|
// loop:
|
|
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_BLOCK_TIMESTAMP.value).putInt(addrBlockTimestamp);
|
|
tempPC = codeByteBuffer.position();
|
|
codeByteBuffer.put(OpCode.BLT_DAT.value).putInt(addrBlockTimestamp).putInt(addrRefundTimestamp).put((byte) (addrTxLoop - tempPC));
|
|
codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrRefund);
|
|
|
|
// txloop:
|
|
assertEquals(addrTxLoop, codeByteBuffer.position());
|
|
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_IN_A.value).putInt(addrLastTimestamp);
|
|
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_IS_ZERO.value).putInt(addrComparator);
|
|
tempPC = codeByteBuffer.position();
|
|
codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(addrComparator).put((byte) (addrCheckTx - tempPC));
|
|
codeByteBuffer.put(OpCode.STP_IMD.value);
|
|
|
|
// checkTx:
|
|
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A.value).putInt(addrLastTimestamp);
|
|
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TYPE_FROM_TX_IN_A.value).putInt(addrTxType);
|
|
tempPC = codeByteBuffer.position();
|
|
codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(addrTxType).put((byte) (addrCheckSender - tempPC));
|
|
codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrTxLoop);
|
|
|
|
// checkSender
|
|
assertEquals(addrCheckSender, codeByteBuffer.position());
|
|
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B.value);
|
|
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B1.value).putInt(addrAddressTemp1);
|
|
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B2.value).putInt(addrAddressTemp2);
|
|
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B3.value).putInt(addrAddressTemp3);
|
|
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B4.value).putInt(addrAddressTemp4);
|
|
tempPC = codeByteBuffer.position();
|
|
codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp1).putInt(addrAddressPart1).put((byte) (addrTxLoop - tempPC));
|
|
tempPC = codeByteBuffer.position();
|
|
codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp2).putInt(addrAddressPart2).put((byte) (addrTxLoop - tempPC));
|
|
tempPC = codeByteBuffer.position();
|
|
codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp3).putInt(addrAddressPart3).put((byte) (addrTxLoop - tempPC));
|
|
tempPC = codeByteBuffer.position();
|
|
codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp4).putInt(addrAddressPart4).put((byte) (addrTxLoop - tempPC));
|
|
|
|
// checkMessage:
|
|
assertEquals(addrCheckMessage, codeByteBuffer.position());
|
|
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B.value);
|
|
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.SWAP_A_AND_B.value);
|
|
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(addrHashPart1);
|
|
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(addrHashPart2);
|
|
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(addrHashPart3);
|
|
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B4.value).putInt(addrHashPart4);
|
|
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_SHA256_A_WITH_B.value).putInt(addrComparator);
|
|
tempPC = codeByteBuffer.position();
|
|
codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(addrComparator).put((byte) (addrPayout - tempPC));
|
|
codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrTxLoop);
|
|
|
|
// payout:
|
|
assertEquals(addrPayout, codeByteBuffer.position());
|
|
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(addrAddressPart1);
|
|
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(addrAddressPart2);
|
|
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(addrAddressPart3);
|
|
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B4.value).putInt(addrAddressPart4);
|
|
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.MESSAGE_A_TO_ADDRESS_IN_B.value);
|
|
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B.value);
|
|
codeByteBuffer.put(OpCode.FIN_IMD.value);
|
|
|
|
// refund:
|
|
assertEquals(addrRefund, codeByteBuffer.position());
|
|
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_CREATOR_INTO_B.value);
|
|
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B.value);
|
|
codeByteBuffer.put(OpCode.FIN_IMD.value);
|
|
|
|
byte[] savedState = simulate();
|
|
|
|
while (!state.getIsFinished()) {
|
|
((ACCTAPI) state.getAPI()).generateNextBlock(secret);
|
|
|
|
savedState = continueSimulation(savedState);
|
|
}
|
|
}
|
|
|
|
}
|