/******************************************************************************
* Copyright © 2013-2016 The Nxt Core Developers. *
* *
* See the AUTHORS.txt, DEVELOPER-AGREEMENT.txt and LICENSE.txt files at *
* the top-level directory of this distribution for the individual copyright *
* holder information and the developer policies on copyright and licensing. *
* *
* Unless otherwise agreed in a custom licensing agreement, no part of the *
* Nxt software, including this file, may be copied, modified, propagated, *
* or distributed except according to the terms contained in the LICENSE.txt *
* file. *
* *
* Removal or modification of this copyright notice is prohibited. *
* *
******************************************************************************/
package nxt.http.accountControl;
import nxt.BlockchainTest;
import nxt.Constants;
import nxt.Nxt;
import nxt.PhasingParams;
import nxt.VoteWeighting.MinBalanceModel;
import nxt.VoteWeighting.VotingModel;
import nxt.http.APICall;
import nxt.http.APICall.Builder;
import nxt.util.Convert;
import nxt.util.Logger;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.junit.Assert;
import org.junit.Test;
import java.util.Arrays;
public class PhasingOnlyTest extends BlockchainTest {
@Test
public void testSetAndGet() throws Exception {
assertNoPhasingOnlyControl();
setPhasingOnlyControl(VotingModel.ACCOUNT, null, 1L, null, null, new long[] {BOB.getId()},
10 * Constants.ONE_NXT, 5, 1440);
assertPhasingOnly(new PhasingParams(VotingModel.ACCOUNT.getCode(), 0L, 1L, 0L, (byte)0, new long[] {BOB.getId()}),
10 * Constants.ONE_NXT, 5, 1440);
}
@Test
public void testAccountVoting() throws Exception {
//all transactions must be approved either by BOB or CHUCK
setPhasingOnlyControl(VotingModel.ACCOUNT, null, 1L, null, null, new long[] {BOB.getId(), CHUCK.getId()}, 0, 0, 0);
Builder builder = new ACTestUtils.Builder("sendMoney", ALICE.getSecretPhrase())
.recipient(BOB.getId())
.param("amountNQT", 1 * Constants.ONE_NXT);
//no phasing - block
ACTestUtils.assertTransactionBlocked(builder);
//correct phasing
setTransactionPhasingParams(builder, 20, VotingModel.ACCOUNT, null, 1L, null, null, new long[] {BOB.getId(), CHUCK.getId()});
ACTestUtils.assertTransactionSuccess(builder);
//subset of the voters should also be blocked
setTransactionPhasingParams(builder, 20, VotingModel.ACCOUNT, null, 1L, null, null, new long[] {BOB.getId()});
ACTestUtils.assertTransactionBlocked(builder);
//incorrect quorum - even if more restrictive, should also be blocked
setTransactionPhasingParams(builder, 20, VotingModel.ACCOUNT, null, 2L, null, null, new long[] {BOB.getId(), CHUCK.getId()});
ACTestUtils.assertTransactionBlocked(builder);
//remove the phasing control
builder = new ACTestUtils.Builder("setPhasingOnlyControl", ALICE.getSecretPhrase());
setControlPhasingParams(builder, VotingModel.NONE, null, null, null, null, null, 0, 0, 0);
setTransactionPhasingParams(builder, 3, VotingModel.ACCOUNT, null, 1L, null, null, new long[] {BOB.getId(), CHUCK.getId()});
JSONObject removePhasingOnlyJSON = ACTestUtils.assertTransactionSuccess(builder);
generateBlock();
assertPhasingOnly(new PhasingParams(VotingModel.ACCOUNT.getCode(), 0L, 1L, 0L, (byte)0, new long[] {BOB.getId(), CHUCK.getId()}), 0, 0, 0);
String fullHash = (String) removePhasingOnlyJSON.get("fullHash");
//approve the remove
builder = new ACTestUtils.Builder("approveTransaction", BOB.getSecretPhrase())
.param("transactionFullHash", fullHash);
ACTestUtils.assertTransactionSuccess(builder);
generateBlock();
assertNoPhasingOnlyControl();
}
@Test
public void testExtraRestrictions() throws Exception {
//all transactions must be approved either by BOB or CHUCK, total fees 5 NXT, min duration 4, max duration 100
setPhasingOnlyControl(VotingModel.ACCOUNT, null, 1L, null, null, new long[] {BOB.getId(), CHUCK.getId()},
5 * Constants.ONE_NXT, 4, 100);
Builder builder = new ACTestUtils.Builder("sendMoney", ALICE.getSecretPhrase())
.recipient(BOB.getId())
.param("amountNQT", 1 * Constants.ONE_NXT)
.feeNQT(7 * Constants.ONE_NXT);
// fee too high
setTransactionPhasingParams(builder, 20, VotingModel.ACCOUNT, null, 1L, null, null, new long[] {BOB.getId(), CHUCK.getId()});
ACTestUtils.assertTransactionBlocked(builder);
// fee at the limit
builder.feeNQT(5 * Constants.ONE_NXT);
JSONObject response = ACTestUtils.assertTransactionSuccess(builder);
String fullHash = (String)response.get("fullHash");
generateBlock();
// not yet approved, another transaction at max fee should fail
ACTestUtils.assertTransactionBlocked(builder);
//approve
Builder approveBuilder = new ACTestUtils.Builder("approveTransaction", BOB.getSecretPhrase())
.param("transactionFullHash", fullHash);
ACTestUtils.assertTransactionSuccess(approveBuilder);
generateBlock();
//now can submit next transaction
response = ACTestUtils.assertTransactionSuccess(builder);
fullHash = (String)response.get("fullHash");
generateBlock();
//approve
approveBuilder.param("transactionFullHash", fullHash);
ACTestUtils.assertTransactionSuccess(approveBuilder);
generateBlock();
//too long or too short periods should fail
builder.param("phasingFinishHeight", Nxt.getBlockchain().getHeight() + 200);
ACTestUtils.assertTransactionBlocked(builder);
builder.param("phasingFinishHeight", Nxt.getBlockchain().getHeight() + 3);
ACTestUtils.assertTransactionBlocked(builder);
builder.param("phasingFinishHeight", Nxt.getBlockchain().getHeight() + 4);
ACTestUtils.assertTransactionSuccess(builder);
}
@Test
public void testRejectingPendingTransaction() throws Exception {
Builder builder = new ACTestUtils.Builder("sendMoney", ALICE.getSecretPhrase())
.recipient(BOB.getId())
.param("amountNQT", 1 * Constants.ONE_NXT);
setTransactionPhasingParams(builder, 4, VotingModel.ACCOUNT, null, 1L, null, null, new long[] {BOB.getId(), CHUCK.getId()});
JSONObject sendMoneyJSON = ACTestUtils.assertTransactionSuccess(builder);
generateBlock();
builder = new ACTestUtils.Builder("setPhasingOnlyControl", ALICE.getSecretPhrase());
setControlPhasingParams(builder, VotingModel.ACCOUNT, null, 1L, null, null, new long[] {DAVE.getId()}, 0, 0, 0);
ACTestUtils.assertTransactionSuccess(builder);
generateBlock();
long balanceBeforeTransactionRejection = ACTestUtils.getAccountBalance(ALICE.getId(), "unconfirmedBalanceNQT");
String fullHash = (String) sendMoneyJSON.get("fullHash");
//approve the pending transaction
builder = new ACTestUtils.Builder("approveTransaction", BOB.getSecretPhrase())
.param("transactionFullHash", fullHash);
ACTestUtils.assertTransactionSuccess(builder);
generateBlock();
//the sendMoney finish height
generateBlock();
//Assert the unconfirmed balance is recovered
Assert.assertEquals(balanceBeforeTransactionRejection + 1 * Constants.ONE_NXT,
ACTestUtils.getAccountBalance(ALICE.getId(), "unconfirmedBalanceNQT"));
}
@Test
public void testBalanceVoting() {
setPhasingOnlyControl(VotingModel.NQT, null, 100 * Constants.ONE_NXT, null, null, null, 0, 0, 0);
Builder builder = new ACTestUtils.Builder("sendMoney", ALICE.getSecretPhrase())
.recipient(BOB.getId())
.param("amountNQT", 1 * Constants.ONE_NXT);
//no phasing - block
ACTestUtils.assertTransactionBlocked(builder);
setTransactionPhasingParams(builder, 20, VotingModel.NQT, null, 100 * Constants.ONE_NXT, null, null, new long[] {DAVE.getId()});
ACTestUtils.assertTransactionBlocked(builder);
setTransactionPhasingParams(builder, 20, VotingModel.ACCOUNT, null, 1L, null, null, new long[] {BOB.getId(), CHUCK.getId()});
ACTestUtils.assertTransactionBlocked(builder);
setTransactionPhasingParams(builder, 20, VotingModel.NQT, null, 100 * Constants.ONE_NXT + 1, null, null, null);
ACTestUtils.assertTransactionBlocked(builder);
builder = new ACTestUtils.Builder("sendMoney", ALICE.getSecretPhrase())
.recipient(BOB.getId())
.param("amountNQT", 1 * Constants.ONE_NXT);
setTransactionPhasingParams(builder, 20, VotingModel.NQT, null, 100 * Constants.ONE_NXT, null, null, null);
ACTestUtils.assertTransactionSuccess(builder);
}
@Test
public void testAssetVoting() {
Builder builder = new ACTestUtils.AssetBuilder(ALICE.getSecretPhrase(), "TestAsset");
String assetId = (String) ACTestUtils.assertTransactionSuccess(builder).get("transaction");
generateBlock();
builder = new ACTestUtils.AssetBuilder(ALICE.getSecretPhrase(), "TestAsset2");
String asset2Id = (String) ACTestUtils.assertTransactionSuccess(builder).get("transaction");
generateBlock();
setPhasingOnlyControl(VotingModel.ASSET, assetId, 100L, null, null, null, 0, 0, 0);
builder = new ACTestUtils.Builder("sendMoney", ALICE.getSecretPhrase())
.recipient(BOB.getId())
.param("amountNQT", 1 * Constants.ONE_NXT);
ACTestUtils.assertTransactionBlocked(builder);
setTransactionPhasingParams(builder, 20, VotingModel.ASSET, asset2Id, 100L, null, null, null);
ACTestUtils.assertTransactionBlocked(builder);
setTransactionPhasingParams(builder, 20, VotingModel.ASSET, assetId, 100L, null, null, null);
ACTestUtils.assertTransactionSuccess(builder);
}
@Test
public void testCurrencyVoting() {
Builder builder = new ACTestUtils.CurrencyBuilder().naming("testa", "TESTA", "Test AC");
String currencyId = (String) ACTestUtils.assertTransactionSuccess(builder).get("transaction");
generateBlock();
builder = new ACTestUtils.CurrencyBuilder().naming("testb", "TESTB", "Test AC");
String currency2Id = (String) ACTestUtils.assertTransactionSuccess(builder).get("transaction");
generateBlock();
setPhasingOnlyControl(VotingModel.CURRENCY, currencyId, 100L, null, null, null, 0, 0, 0);
builder = new ACTestUtils.Builder("sendMoney", ALICE.getSecretPhrase())
.recipient(BOB.getId())
.param("amountNQT", 1 * Constants.ONE_NXT);
ACTestUtils.assertTransactionBlocked(builder);
setTransactionPhasingParams(builder, 20, VotingModel.CURRENCY, currency2Id, 100L, null, null, null);
ACTestUtils.assertTransactionBlocked(builder);
setTransactionPhasingParams(builder, 20, VotingModel.CURRENCY, currencyId, 100L, null, null, null);
ACTestUtils.assertTransactionSuccess(builder);
}
private void assertNoPhasingOnlyControl() {
Builder builder = new APICall.Builder("getPhasingOnlyControl")
.param("account", Long.toUnsignedString(ALICE.getId()));
JSONObject response = builder.build().invoke();
Assert.assertTrue(response.isEmpty());
}
private void assertPhasingOnly(PhasingParams expected, long maxFees, int minDuration, int maxDuration) {
Builder builder = new APICall.Builder("getPhasingOnlyControl")
.param("account", Long.toUnsignedString(ALICE.getId()));
JSONObject response = builder.build().invoke();
Logger.logMessage("getPhasingOnlyControl response: " + response.toJSONString());
Assert.assertEquals(expected.getVoteWeighting().getVotingModel().getCode(), ((Long) response.get("votingModel")).byteValue());
Assert.assertEquals(expected.getQuorum(), Convert.parseLong(response.get("quorum")));
Assert.assertEquals(expected.getWhitelist().length, ((JSONArray) response.get("whitelist")).size());
Assert.assertEquals(expected.getVoteWeighting().getHoldingId(), Convert.parseUnsignedLong((String)response.get("holding")));
Assert.assertEquals(expected.getVoteWeighting().getMinBalance(), Convert.parseLong(response.get("minBalance")));
Assert.assertEquals(expected.getVoteWeighting().getMinBalanceModel().getCode(), ((Long) response.get("minBalanceModel")).byteValue());
Assert.assertEquals(maxFees, Convert.parseLong(response.get("maxFees")));
Assert.assertEquals(minDuration, ((Long)response.get("minDuration")).shortValue());
Assert.assertEquals(maxDuration, ((Long)response.get("maxDuration")).shortValue());
}
private void setPhasingOnlyControl(VotingModel votingModel, String holdingId, Long quorum,
Long minBalance, MinBalanceModel minBalanceModel, long[] whitelist,
long maxFees, int minDuration, int maxDuration) {
Builder builder = new ACTestUtils.Builder("setPhasingOnlyControl", ALICE.getSecretPhrase());
setControlPhasingParams(builder, votingModel, holdingId, quorum,
minBalance, minBalanceModel, whitelist, maxFees, minDuration, maxDuration);
APICall apiCall = builder.build();
JSONObject response = apiCall.invoke();
Logger.logMessage("setPhasingOnlyControl response: " + response.toJSONString());
String result = (String) response.get("transaction");
Assert.assertNotNull(result);
generateBlock();
}
private void setControlPhasingParams(Builder builder,
VotingModel votingModel, String holdingId, Long quorum,
Long minBalance, MinBalanceModel minBalanceModel, long[] whitelist,
long maxFees, int minDuration, int maxDuration) {
if (votingModel != null) {
builder.param("controlVotingModel", votingModel.getCode());
}
if (holdingId != null) {
builder.param("controlHolding", holdingId);
}
if (quorum != null) {
builder.param("controlQuorum", quorum);
}
if (minBalance != null) {
builder.param("controlMinBalance", minBalance);
}
if (minBalanceModel != null) {
builder.param("controlMinBalanceModel", minBalanceModel.getCode());
}
if (whitelist != null) {
builder.param("controlWhitelisted", Arrays.stream(whitelist).mapToObj(l -> Long.toUnsignedString(l)).toArray(String[]::new));
}
if (maxFees > 0) {
builder.param("controlMaxFees", maxFees);
}
if (minDuration > 0) {
builder.param("controlMinDuration", minDuration);
}
if (maxDuration > 0) {
builder.param("controlMaxDuration", maxDuration);
}
}
private void setTransactionPhasingParams(Builder builder, int finishAfter, VotingModel votingModel, String holdingId, Long quorum,
Long minBalance, MinBalanceModel minBalanceModel, long[] whitelist) {
builder.param("phased", "true");
builder.param("phasingVotingModel", votingModel.getCode());
builder.param("phasingFinishHeight", Nxt.getBlockchain().getHeight() + finishAfter);
if (holdingId != null) {
builder.param("phasingHolding", holdingId);
}
if (quorum != null) {
builder.param("phasingQuorum", quorum);
}
if (minBalance != null) {
builder.param("phasingMinBalance", minBalance);
}
if (minBalanceModel != null) {
builder.param("phasingMinBalanceModel", minBalanceModel.getCode());
}
if (whitelist != null) {
builder.param("phasingWhitelisted", Arrays.stream(whitelist).mapToObj(l -> Long.toUnsignedString(l)).toArray(String[]::new));
}
}
}