Skip to content

Commit

Permalink
Implement base juice cost per transaction.
Browse files Browse the repository at this point in the history
  • Loading branch information
mikera committed Sep 22, 2023
1 parent 68b7d3a commit 6419188
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 41 deletions.
5 changes: 5 additions & 0 deletions convex-core/src/main/java/convex/core/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public class Constants {
*/
public static final long INITIAL_TIMESTAMP = Instant.parse("2020-02-02T00:20:20.0202Z").toEpochMilli();

/**
* Base juice cost per top level transaction
*/
public static final long BASE_TRANSACTION_JUICE = 500L;

/**
* Juice price in the initial Genesis State
*/
Expand Down
5 changes: 4 additions & 1 deletion convex-core/src/main/java/convex/core/State.java
Original file line number Diff line number Diff line change
Expand Up @@ -556,11 +556,14 @@ private Context prepareTransaction(ATransaction t, long juicePrice) {
long balance=account.getBalance();
long juiceLimit=Juice.calcAvailable(balance, juicePrice);
juiceLimit=Math.min(Constants.MAX_TRANSACTION_JUICE,juiceLimit);
if (juiceLimit<=0) {
long initialJuice=Constants.BASE_TRANSACTION_JUICE;
if (juiceLimit<=initialJuice) {
return Context.createFake(this,origin).withJuiceError();
}

// Create context ready to execute, with at least some available juice
Context ctx = Context.createInitial(this, origin, juiceLimit);
ctx=ctx.withJuice(initialJuice);
return ctx;
}

Expand Down
1 change: 1 addition & 0 deletions convex-core/src/main/java/convex/core/lang/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -2307,4 +2307,5 @@ public AFn<ACell> lookupExpander(ACell form) {
}
return null;
}

}
5 changes: 0 additions & 5 deletions convex-core/src/main/java/convex/core/lang/Juice.java
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,6 @@ public class Juice {
*/
public static final long TRANSFER = 200;

/**
* Base juice for any signed transaction
*/
public static final long TRANSACTION_BASE = 1000;

/**
* Juice per byte for any signed transaction
*/
Expand Down
62 changes: 33 additions & 29 deletions convex-core/src/test/java/convex/core/StateTransitionsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,19 @@ public class StateTransitionsTest {
final Address ADDRESS_C = Address.create(3);

final Address ADDRESS_NIKI = Address.create(4);

final long ABAL=10000;
final long BBAL=2000;

@Test
public void testAccountTransfers() throws BadSignatureException {
AccountKey ka=KEYPAIR_A.getAccountKey();
AccountKey kb=KEYPAIR_B.getAccountKey();
long STAKE=Constants.MINIMUM_EFFECTIVE_STAKE*10;
AVector<AccountStatus> accounts = Vectors.of(
AccountStatus.create(10000L,ka).withMemory(10000),
AccountStatus.create(1000L,kb).withMemory(10000),
AccountStatus.create(Constants.MAX_SUPPLY - STAKE - 10000 - 1000,KEYPAIR_ROBB.getAccountKey()).withMemory(10000)
AccountStatus.create(ABAL,ka).withMemory(10000),
AccountStatus.create(BBAL,kb).withMemory(10000),
AccountStatus.create(Constants.MAX_SUPPLY - STAKE - ABAL - BBAL,KEYPAIR_ROBB.getAccountKey()).withMemory(10000)
// No account for C yet
);
State s = State.EMPTY.withAccounts(accounts); // don't need any peers for these tests
Expand All @@ -69,42 +72,44 @@ public void testAccountTransfers() throws BadSignatureException {
s=s.updateMemoryPool(0, 0); // clear memory pool so doesn't confuse things
assertEquals(Constants.MAX_SUPPLY, s.computeTotalFunds());

assertEquals(10000, s.getBalance(ADDRESS_A));
assertEquals(1000, s.getBalance(ADDRESS_B));
assertEquals(ABAL, s.getBalance(ADDRESS_A));
assertEquals(BBAL, s.getBalance(ADDRESS_B));
assertNull(s.getBalance(ADDRESS_C));

long TCOST = Juice.TRANSFER * s.getJuicePrice().longValue();

long JPRICE=s.getJuicePrice().longValue(); // Juice price
long TCOST = (Constants.BASE_TRANSACTION_JUICE+ Juice.TRANSFER) * JPRICE;
long AMT=50; // Amount for small transfers

{ // transfer from existing to existing account A->B
Transfer t1 = Transfer.create(ADDRESS_A,1, ADDRESS_B, 50);
Transfer t1 = Transfer.create(ADDRESS_A,1, ADDRESS_B, AMT);
SignedData<ATransaction> st = KEYPAIR_A.signData(t1);
long nowTS = Utils.getCurrentTimestamp();
long nowTS = Constants.INITIAL_TIMESTAMP;
Block b = Block.of(nowTS, st);
SignedData<Block> sb=KEYPAIR_A.signData(b);
BlockResult br = s.applyBlock(sb);
AVector<Result> results = br.getResults();
assertEquals(1, results.count());
assertNull(br.getErrorCode(0),br.getResult(0).toString()); // should be null for successful transfer transaction
State s2 = br.getState();
assertEquals(9950 - TCOST, s2.getBalance(ADDRESS_A));
assertEquals(1050, s2.getBalance(ADDRESS_B));
assertEquals(ABAL - TCOST - AMT, s2.getBalance(ADDRESS_A));
assertEquals(BBAL + AMT, s2.getBalance(ADDRESS_B));
assertCVMEquals(nowTS, s2.getTimestamp());
}

{ // transfer from existing to non-existing account A -> C
Transfer t1 = Transfer.create(ADDRESS_A,1, ADDRESS_C, 50);
Transfer t1 = Transfer.create(ADDRESS_A,1, ADDRESS_C, AMT);
SignedData<ATransaction> st = KEYPAIR_A.signData(t1);
Block b = Block.of(System.currentTimeMillis(), st);
SignedData<Block> sb=KEYPAIR_A.signData(b);
State s2 = s.applyBlock(sb).getState();

// no transfer should have happened, although cost should have been paid
assertEquals(10000 - TCOST, s2.getBalance(ADDRESS_A));
assertEquals(ABAL - TCOST, s2.getBalance(ADDRESS_A));
assertNull(s2.getBalance(ADDRESS_C));
}

{ // transfer from a non-existent address
Transfer t1 = Transfer.create(ADDRESS_C,1, ADDRESS_B, 50);
Transfer t1 = Transfer.create(ADDRESS_C,1, ADDRESS_B, AMT);
SignedData<ATransaction> st = KEYPAIR_C.signData(t1);
Block b = Block.of(System.currentTimeMillis(), st);
SignedData<Block> sb=KEYPAIR_A.signData(b);
Expand All @@ -117,51 +122,51 @@ public void testAccountTransfers() throws BadSignatureException {
// First create new account C
State s0=s.putAccount(ADDRESS_C, AccountStatus.create(0L,KEYPAIR_C.getAccountKey()));

Transfer t1 = Transfer.create(ADDRESS_A,1, ADDRESS_C, 50);
Transfer t1 = Transfer.create(ADDRESS_A,1, ADDRESS_C, AMT);
SignedData<ATransaction> st = KEYPAIR_A.signData(t1);
Block b = Block.of(System.currentTimeMillis(), st);
SignedData<Block> sb=KEYPAIR_A.signData(b);
State s2 = s0.applyBlock(sb).getState();

// Transfer should have happened
assertEquals(9950 - TCOST, s2.getBalance(ADDRESS_A));
assertEquals(50, s2.getBalance(ADDRESS_C));
assertEquals(ABAL - TCOST - AMT, s2.getBalance(ADDRESS_A));
assertEquals(AMT, s2.getBalance(ADDRESS_C));
}

{ // two transfers in sequence, both from A -> C
// First create new account C
State s0=s.putAccount(ADDRESS_C, AccountStatus.create(0L,KEYPAIR_C.getAccountKey()));

Transfer t1 = Transfer.create(ADDRESS_A,1, ADDRESS_C, 150);
Transfer t1 = Transfer.create(ADDRESS_A,1, ADDRESS_C, AMT*3);
SignedData<ATransaction> st1 = KEYPAIR_A.signData(t1);
Transfer t2 = Transfer.create(ADDRESS_A,2, ADDRESS_C, 150);
Transfer t2 = Transfer.create(ADDRESS_A,2, ADDRESS_C, AMT*2);
SignedData<ATransaction> st2 = KEYPAIR_A.signData(t2);
Block b = Block.of(System.currentTimeMillis(), st1, st2);
SignedData<Block> sb=KEYPAIR_A.signData(b);

BlockResult br = s0.applyBlock(sb);
State s2 = br.getState();
assertEquals(9700 - TCOST * 2, s2.getBalance(ADDRESS_A));
assertEquals(1000, s2.getBalance(ADDRESS_B));
assertEquals(300, s2.getBalance(ADDRESS_C));
assertEquals(ABAL - AMT*5 - TCOST * 2, s2.getBalance(ADDRESS_A));
assertEquals(BBAL, s2.getBalance(ADDRESS_B));
assertEquals(AMT*5, s2.getBalance(ADDRESS_C));
}

{ // two transfers in sequence, 2 different accounts A B --> new account C
// First create new account C
State s0=s.putAccount(ADDRESS_C, AccountStatus.create(0L,KEYPAIR_C.getAccountKey()));

Transfer t1 = Transfer.create(ADDRESS_A,1, ADDRESS_C, 50);
Transfer t1 = Transfer.create(ADDRESS_A,1, ADDRESS_C, AMT);
SignedData<ATransaction> st1 = KEYPAIR_A.signData(t1);
Transfer t2 = Transfer.create(ADDRESS_B,1, ADDRESS_C, 50);
Transfer t2 = Transfer.create(ADDRESS_B,1, ADDRESS_C, AMT);
SignedData<ATransaction> st2 = KEYPAIR_B.signData(t2);
Block b = Block.of(System.currentTimeMillis(), st1, st2);
SignedData<Block> sb=KEYPAIR_A.signData(b);

BlockResult br = s0.applyBlock(sb);
State s2 = br.getState();
assertEquals(9950 - TCOST, s2.getBalance(ADDRESS_A));
assertEquals(950 - TCOST, s2.getBalance(ADDRESS_B));
assertEquals(100, s2.getBalance(ADDRESS_C));
assertEquals(ABAL - AMT - TCOST, s2.getBalance(ADDRESS_A));
assertEquals(BBAL - AMT - TCOST, s2.getBalance(ADDRESS_B));
assertEquals(2*AMT, s2.getBalance(ADDRESS_C));

AVector<Result> results = br.getResults();
assertEquals(2, results.count());
Expand All @@ -170,7 +175,7 @@ public void testAccountTransfers() throws BadSignatureException {
}

{ // transfer with an incorrect sequence number
Transfer t1 = Transfer.create(ADDRESS_A,2, ADDRESS_C, 50);
Transfer t1 = Transfer.create(ADDRESS_A,2, ADDRESS_C, AMT);
SignedData<ATransaction> st = KEYPAIR_A.signData(t1);
Block b = Block.of(System.currentTimeMillis(), st);
SignedData<Block> sb=KEYPAIR_A.signData(b);
Expand Down Expand Up @@ -202,7 +207,6 @@ public void testAccountTransfers() throws BadSignatureException {
// System.out.println(ADDRESS_NIKI);
// System.out.println("Niki has "+s.getBalance(ADDRESS_NIKI).getValue());

long AMT = 500;
// System.out.println("Tansferring "+AMT+" to Niki");

Transfer t1 = Transfer.create(ADDRESS_A,1, ADDRESS_NIKI, AMT);
Expand Down
7 changes: 4 additions & 3 deletions convex-core/src/test/java/convex/core/TransactionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ protected State apply(ATransaction t) {

@Test
public void testTransfer() {
Transfer t1=Transfer.create(HERO, 1, VILLAIN, 1000);
long AMT=999;
Transfer t1=Transfer.create(HERO, 1, VILLAIN, AMT);
State s=apply(t1);
long expectedFees=Juice.TRANSFER*JP;
assertEquals(1000+expectedFees,state().getAccount(HERO).getBalance()-s.getAccount(HERO).getBalance());
long expectedFees=(Constants.BASE_TRANSACTION_JUICE+Juice.TRANSFER)*JP;
assertEquals(AMT+expectedFees,state().getAccount(HERO).getBalance()-s.getAccount(HERO).getBalance());
assertEquals(expectedFees,s.getGlobalFees().longValue());

// We expect a Transfer to be completely encoded
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ public void testSingleBlockConsensus() throws Exception {
long INITIAL_BALANCE_PROPOSER = INITIAL_STATE.getBalance(PADDRESS);
long INITIAL_BALANCE_RECEIVER = INITIAL_STATE.getBalance(RADDRESS);
long TRANSFER_AMOUNT = 100;
long TJUICE=Juice.TRANSFER;
long TJUICE=Constants.BASE_TRANSACTION_JUICE+Juice.TRANSFER;

ATransaction trans = Transfer.create(PADDRESS, 1, RADDRESS, TRANSFER_AMOUNT); // note 1 = first sequence number required
Peer[] bs3 = proposeTransactions(bs2, PROPOSER, trans);
Expand Down Expand Up @@ -309,7 +309,7 @@ public void testMultiBlockConsensus() throws Exception {
AccountKey RKEY = KEYS[RECEIVER];
Long INITIAL_BALANCE_PROPOSER = INITIAL_STATE.getBalance(PADDRESS);
Long INITIAL_BALANCE_RECEIVER = INITIAL_STATE.getBalance(RADDRESS);
long TJUICE=Juice.TRANSFER;
long TJUICE=Constants.BASE_TRANSACTION_JUICE+Juice.TRANSFER;

Peer[] bs3 = bs2;
for (int i = 0; i < NUM_PEERS; i++) {
Expand Down Expand Up @@ -412,7 +412,7 @@ public void testGossipConsensus() throws Exception {
AccountKey RKEY = KEYS[RECEIVER];
long INITIAL_BALANCE_PROPOSER = INITIAL_STATE.getBalance(PADDRESS);
long INITIAL_BALANCE_RECEIVER = INITIAL_STATE.getBalance(RADDRESS);
long expectedJuice=Juice.TRANSFER*(NUM_INITIAL_TRANS+TX_ROUNDS);
long expectedJuice=(Constants.BASE_TRANSACTION_JUICE+Juice.TRANSFER)*(NUM_INITIAL_TRANS+TX_ROUNDS);


Peer[] bs3 = bs2;
Expand Down
12 changes: 12 additions & 0 deletions convex-core/src/test/java/convex/core/lang/JuiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

import convex.core.data.Address;

import static convex.test.Assertions.*;

/**
Expand Down Expand Up @@ -41,6 +44,15 @@ public void testExpandJuice() {
assertEquals(Juice.EXPAND_SEQUENCE + Juice.EXPAND_CONSTANT * 3, juiceExpand("[1 2 3]")); // [1 2 3] -> (vector 1
// 2 3)
}

@Test
public void testJuiceLimit() {
Context ctx=context();
Address D=VILLAIN;

// Shouldn't be able to afford sending full balance
assertJuiceError(step(ctx,"(transfer "+D+" *balance*)"));
}

@Test
public void testEval() {
Expand Down

0 comments on commit 6419188

Please sign in to comment.