convex.core.State Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of convex-core Show documentation
Show all versions of convex-core Show documentation
Convex core libraries and common utilities
The newest version!
package convex.core;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import convex.core.data.ABlob;
import convex.core.data.ACell;
import convex.core.data.AMap;
import convex.core.data.ARecord;
import convex.core.data.AVector;
import convex.core.data.AccountKey;
import convex.core.data.AccountStatus;
import convex.core.data.Address;
import convex.core.data.Blob;
import convex.core.data.Cells;
import convex.core.data.Format;
import convex.core.data.Hash;
import convex.core.data.IRefFunction;
import convex.core.data.Index;
import convex.core.data.Keyword;
import convex.core.data.Keywords;
import convex.core.data.LongBlob;
import convex.core.data.MapEntry;
import convex.core.data.PeerStatus;
import convex.core.data.Ref;
import convex.core.data.SignedData;
import convex.core.data.Strings;
import convex.core.data.Symbol;
import convex.core.data.Tag;
import convex.core.data.Vectors;
import convex.core.data.prim.CVMLong;
import convex.core.exceptions.BadFormatException;
import convex.core.exceptions.InvalidDataException;
import convex.core.lang.AOp;
import convex.core.lang.Context;
import convex.core.lang.Juice;
import convex.core.lang.RT;
import convex.core.lang.RecordFormat;
import convex.core.lang.Symbols;
import convex.core.transactions.ATransaction;
import convex.core.util.Counters;
import convex.core.util.Economics;
import convex.core.util.Utils;
/**
* Class representing the immutable state of the CVM
*
* State transitions are represented by blocks of transactions, according to the logic: s[n+1] = s[n].applyBlock(b[n])
*
* State contains the following elements - Map of AccountStatus for every
* Address - Map of PeerStatus for every Peer Address - Global values - Schedule
* data structure
*
* "State. You're doing it wrong" - Rich Hickey
*
*/
public class State extends ARecord {
public static final Index> EMPTY_SCHEDULE = Index.none();
public static final Index EMPTY_PEERS = Index.none();
private static final Keyword[] STATE_KEYS = new Keyword[] { Keywords.ACCOUNTS, Keywords.PEERS,
Keywords.GLOBALS, Keywords.SCHEDULE };
private static final RecordFormat FORMAT = RecordFormat.of(STATE_KEYS);
/**
* Symbols for global values in :globals Vector
*/
public static final AVector GLOBAL_SYMBOLS=Vectors.of(
Symbols.TIMESTAMP,
Symbols.FEES,
Symbols.JUICE_PRICE,
Symbols.MEMORY,
Symbols.MEMORY_VALUE,
Symbols.PROTOCOL);
// Indexes for globals in :globals Vector
public static final int GLOBAL_TIMESTAMP=0;
public static final int GLOBAL_FEES=1;
public static final int GLOBAL_JUICE_PRICE=2;
public static final int GLOBAL_MEMORY_MEM=3;
public static final int GLOBAL_MEMORY_CVX=4;
public static final int GLOBAL_PROTOCOL=5; // TODO: move to actor
/**
* An empty State
*/
public static final State EMPTY = create(Vectors.empty(), EMPTY_PEERS, Constants.INITIAL_GLOBALS, EMPTY_SCHEDULE);
private static final Logger log = LoggerFactory.getLogger(State.class.getName());
// Note: we are embedding these directly in the State cell.
// TODO: check we aren't at risk of hitting max encoding size limits
private final AVector accounts;
private final Index peers;
private final AVector globals;
private final Index> schedule;
private State(AVector accounts, Index peers,
AVector globals, Index> schedule) {
super(FORMAT.count());
this.accounts = accounts;
this.peers = peers;
this.globals = globals;
this.schedule = schedule;
}
@Override
public ACell get(Keyword k) {
if (Keywords.ACCOUNTS.equals(k)) return accounts;
if (Keywords.PEERS.equals(k)) return peers;
if (Keywords.GLOBALS.equals(k)) return globals;
if (Keywords.SCHEDULE.equals(k)) return schedule;
return null;
}
@Override
public int getRefCount() {
int rc=accounts.getRefCount();
rc+=peers.getRefCount();
rc+=globals.getRefCount();
rc+=schedule.getRefCount();
return rc;
}
public Ref getRef(int i) {
if (i<0) throw new IndexOutOfBoundsException(i);
{
int c=accounts.getRefCount();
if (i newAccounts = accounts.updateRefs(func);
Index newPeers = peers.updateRefs(func);
AVector newGlobals = globals.updateRefs(func);
Index> newSchedule = schedule.updateRefs(func);
if ((accounts == newAccounts) && (peers == newPeers) && (globals == newGlobals)
&& (schedule == newSchedule)) {
return this;
}
return new State(newAccounts, newPeers, newGlobals, newSchedule);
}
/**
* Create a State
* @param accounts Accounts
* @param peers Peers
* @param globals Globals
* @param schedule Schedule (may be null)
* @return New State instance
*/
public static State create(AVector accounts, Index peers,
AVector globals, Index> schedule) {
return new State(accounts, peers, globals, schedule);
}
@Override
public int encode(byte[] bs, int pos) {
bs[pos++]=getTag();
return encodeRaw(bs,pos);
}
@Override
public int encodeRaw(byte[] bs, int pos) {
pos = accounts.encode(bs,pos);
pos = peers.encode(bs,pos);
pos = globals.encode(bs,pos);
pos = schedule.encode(bs,pos);
return pos;
}
@Override
public int getEncodingLength() {
int length=1;
length+=accounts.getEncodingLength();
length+=peers.getEncodingLength();
length+=globals.getEncodingLength();
length+=schedule.getEncodingLength();
return length;
}
@Override
public int estimatedEncodingSize() {
int est=1;
est+=accounts.estimatedEncodingSize();
est+=peers.estimatedEncodingSize();
est+=globals.estimatedEncodingSize();
est+=schedule.estimatedEncodingSize();
return est;
}
/**
* Reads a State from an encoding. Assumes tag byte already read.
*
* @param b Blob to read from
* @param pos start position in Blob
* @return Decoded State
* @throws BadFormatException If a State could not be read
*/
public static State read(Blob b, int pos) throws BadFormatException {
int epos=pos+1; // skip tag
AVector accounts = Format.read(b,epos);
if (accounts==null) throw new BadFormatException("Null accounts!");
epos+=Format.getEncodingLength(accounts);
Index peers = Format.read(b,epos);
if (peers==null) throw new BadFormatException("Null peers!");
epos+=Format.getEncodingLength(peers);
AVector globals = Format.read(b,epos);
if (globals==null) throw new BadFormatException("Null globals!");
epos+=Format.getEncodingLength(globals);
Index> schedule = Format.read(b,epos);
if (schedule==null) throw new BadFormatException("Null schedule!");
epos+=Format.getEncodingLength(schedule);
State result=create(accounts, peers, globals, schedule);
result.attachEncoding(b.slice(pos,epos));
return result;
}
/**
* Get all Accounts in this State
* @return Vector of Accounts
*/
public AVector getAccounts() {
return accounts;
}
/**
* Gets the map of Peers for this State
*
* @return A map of addresses to PeerStatus records
*/
public Index getPeers() {
return peers;
}
/**
* Gets the balance of a specific address, or null if the Address does not exist
* @param address Address to check
* @return Long balance, or null if Account does not exist
*/
public Long getBalance(Address address) {
AccountStatus acc = getAccount(address);
if (acc == null) return null;
return acc.getBalance();
}
public State withBalance(Address address, long newBalance) {
AccountStatus acc = getAccount(address);
if (acc == null) {
throw new Error("No account for " + address);
} else {
acc = acc.withBalance(newBalance);
}
return putAccount(address, acc);
}
/**
* Applies a signed Block to the current state, i.e. the State Transition function
*
* @param signedBlock Signed Block to apply
* @return The BlockResult from applying the given Block to this State
*/
public BlockResult applyBlock(SignedData signedBlock) {
Block block=signedBlock.getValue();
Counters.applyBlock++;
// First check the Block passes pre-conditions for application
BlockResult maybeFailed=checkBlock(signedBlock);
if (maybeFailed!=null) {
return maybeFailed;
}
State state = prepareBlock(block);
return state.applyTransactions(block);
}
/**
* Checks if a block is valid for application to the current state
* @param signedBlock Signed Block
* @return BlockResult instance if an error occurred, or nil if checks pass
*/
public BlockResult checkBlock(SignedData signedBlock) {
Block block=signedBlock.getValue();
AccountKey peerKey=signedBlock.getAccountKey();
PeerStatus ps=peers.get(peerKey);
// If no current peer for this block, dump it
if (ps==null) return BlockResult.createInvalidBlock(this,block,Strings.MISSING_PEER);
// If peer stake is insufficient, dump it
if (ps.getPeerStake()Constants.MAX_TRANSACTIONS_PER_BLOCK) {
return BlockResult.createInvalidBlock(this,block,Strings.ILLEGAL_BLOCK_SIZE);
}
return null;
}
/**
* Apply state updates consistent with time advancing to a given timestamp
* @param b Block to apply
* @return Updated state after block preparation
*/
private State prepareBlock(Block b) {
State state = this;
state = state.applyTimeUpdate(b.getTimeStamp());
state = state.applyScheduledTransactions();
return state;
}
/**
* Updates the State based on passage of time.
*
* Note: while this can be applied independently, normal correct State updates
* should go via `applyBlock`
*
* @param newTimestamp New timestamp to apply
* @return Updates State
*/
public State applyTimeUpdate(long newTimestamp) {
State state = this;
AVector glbs = state.globals;
long oldTimestamp=((CVMLong)glbs.get(GLOBAL_TIMESTAMP)).longValue();
// Exit if no time elapsed
if (newTimestamp <= oldTimestamp) return this;
// long elapsed=newTimestamp-oldTimestamp;
// Update timestamp
glbs=glbs.assoc(GLOBAL_TIMESTAMP,CVMLong.create(newTimestamp));
// Grow memory pool if required
long memAdditions=(newTimestamp/Constants.MEMORY_POOL_GROWTH_INTERVAL)-(oldTimestamp/Constants.MEMORY_POOL_GROWTH_INTERVAL);
if (memAdditions>0) {
long mem=((CVMLong)glbs.get(GLOBAL_MEMORY_MEM)).longValue();
long add=memAdditions*Constants.MEMORY_POOL_GROWTH;
if (add>0) {
long newMem=mem+add;
glbs=glbs.assoc(GLOBAL_MEMORY_MEM, CVMLong.create(newMem));
} else {
throw new Error("Bad memory additions?");
}
}
state = state.withGlobals(glbs);
return state;
}
@SuppressWarnings("unchecked")
public State applyScheduledTransactions() {
long tcount = 0;
Index> sched = this.schedule;
CVMLong timestamp = this.getTimestamp();
// ArrayList to accumulate the transactions to apply. Null until we need it
ArrayList al = null;
// walk schedule entries to determine how many there are
// and remove from the current schedule
// we can optimise bulk removal later
while (tcount < Constants.MAX_SCHEDULED_TRANSACTIONS_PER_BLOCK) {
if (sched.isEmpty()) break;
MapEntry> me = sched.entryAt(0);
ABlob key = me.getKey();
long time = key.longValue();
if (time > timestamp.longValue()) break; // exit if we are still in the future
AVector trans = me.getValue();
long numScheduled = trans.count(); // number scheduled at this schedule timestamp
long take = Math.min(numScheduled, Constants.MAX_SCHEDULED_TRANSACTIONS_PER_BLOCK - tcount);
// add scheduled transactions to List
if (al == null) al = new ArrayList<>();
for (long i = 0; i < take; i++) {
al.add(trans.get(i));
}
// remove schedule entries taken. Delete key if no more entries remaining
trans = trans.slice(take, numScheduled);
if (trans.isEmpty()) sched = sched.dissoc(key);
}
if (al==null) return this; // nothing to do if no transactions to execute
// update state with amended schedule
State state = this.withSchedule(sched);
// now apply the transactions!
int n = al.size();
log.debug("Applying {} scheduled transactions",n);
for (int i = 0; i < n; i++) {
AVector st = (AVector) al.get(i);
Address origin = (Address) st.get(0);
AOp> op = (AOp>) st.get(1);
Context ctx;
// TODO juice limit? juice refund?
ctx = Context.create(state, origin, Constants.MAX_TRANSACTION_JUICE);
ctx = ctx.run(op);
if (ctx.isExceptional()) {
// TODO: what to do here? probably ignore
// we maybe need to think about reporting scheduled results?
log.trace("Scheduled transaction error: {}", ctx.getExceptional());
} else {
state = ctx.getState();
log.trace("Scheduled transaction succeeded");
}
}
return state;
}
private State withSchedule(Index> newSchedule) {
if (schedule == newSchedule) return this;
return new State(accounts, peers, globals, newSchedule);
}
private State withGlobals(AVector newGlobals) {
if (newGlobals == globals) return this;
return new State(accounts, peers, newGlobals, schedule);
}
private BlockResult applyTransactions(Block block) {
State state = this;
int blockLength = block.length();
Result[] results = new Result[blockLength];
AVector> transactions = block.getTransactions();
for (int i = 0; i < blockLength; i++) {
// SECURITY: catch-all exception handler, needs consideration
try {
// extract the signed transaction from the block
SignedData extends ATransaction> signed = transactions.get(i);
// execute the transaction using the *latest* state (not necessarily "this")
ResultContext rc = state.applyTransaction(signed);
// record results from result context
results[i] = Result.fromContext(CVMLong.create(i),rc);
// state update
state = rc.context.getState();
} catch (Exception e) {
String msg= "Unexpected fatal exception applying transaction: "+e.toString();
results[i] = Result.create(CVMLong.create(i), Strings.create(msg),ErrorCodes.UNEXPECTED).withSource(SourceCodes.CVM);
log.error(msg,e);
}
}
// TODO: changes for complete block?
return BlockResult.create(state, results);
}
/**
* Applies a signed transaction to the State.
*
* SECURITY: Checks digital signature and correctness of account key
*
* @return ResultContext containing the result of the transaction
*/
public ResultContext applyTransaction(SignedData extends ATransaction> signedTransaction) {
// Extract transaction, performs signature check
ATransaction t=signedTransaction.getValue();
Address addr=t.getOrigin();
AccountStatus as = getAccount(addr);
if (as==null) {
ResultContext rc=ResultContext.error(this,ErrorCodes.NOBODY,"Transaction for non-existent Account: "+addr);
return rc.withSource(SourceCodes.CVM);
} else {
// Update sequence number for target account
long sequence=t.getSequence();
long expectedSequence=as.getSequence()+1;
if (sequence!=expectedSequence) {
ResultContext rc=ResultContext.error(this,ErrorCodes.SEQUENCE, "Sequence = "+sequence+" but expected "+expectedSequence);
return rc.withSource(SourceCodes.CVM);
}
AccountKey key=as.getAccountKey();
if (key==null) {
ResultContext rc= ResultContext.error(this,ErrorCodes.STATE,"Transaction for account that is an Actor: "+addr);
return rc.withSource(SourceCodes.CVM);
}
boolean sigValid=signedTransaction.checkSignature(key);
if (!sigValid) {
ResultContext rc= ResultContext.error(this,ErrorCodes.SIGNATURE, Strings.BAD_SIGNATURE);
return rc.withSource(SourceCodes.CVM);
}
}
ResultContext ctx=applyTransaction(t);
return ctx;
}
/**
* Creates an initial ResultContext for a transaction
* @param t
* @return
*/
private ResultContext createResultContext(ATransaction t) {
long juicePrice=getJuicePrice().longValue();
ResultContext rc=new ResultContext(t,juicePrice);
return rc;
}
/**
* Applies a transaction to the State.
*
* There are three phases in application of a transaction:
*
* - Preparation for accounting, with {@link #prepareTransaction(ResultContext) prepareTransaction}
* - Functional application of the transaction with ATransaction.apply(....)
* - Completion of accounting, with completeTransaction
*
*
* SECURITY: Assumes digital signature and sequence number already checked.
*
* @param t Transaction to apply
* @return Context containing the updated chain State (may be exceptional)
*/
public ResultContext applyTransaction(ATransaction t) {
ResultContext rc=createResultContext(t);
// Create prepared context
Context ctx = prepareTransaction(rc);
if (!ctx.isExceptional()) {
State preparedState=ctx.getState();
// apply transaction. This may result in an error / exceptional result!
ctx = t.apply(ctx);
// Set context, might be needed by completeTransaction
rc.context=ctx;
// complete transaction including handling of fees
// NOTE: completeTransaction handles error cases as well
ctx = ctx.completeTransaction(preparedState,rc);
} else {
// We hit some error while preparing transaction. Possible culprits:
// - Non-existent Origin account
// - Bad sequence number
// Return context with no change, i.e. before executing the transaction
rc.source=SourceCodes.CVM;
}
return rc.withContext(ctx);
}
/**
* Prepares a CVM execution context and ResultContext for a transaction
* @param rc ResultContext to populate
* @return
*/
private Context prepareTransaction(ResultContext rc) {
ATransaction t=rc.tx;
long juicePrice=rc.juicePrice;
Address origin = t.getOrigin();
// Pre-transaction state updates (persisted even if transaction fails)
AccountStatus account = getAccount(origin);
if (account == null) {
return Context.create(this).withError(ErrorCodes.NOBODY);
}
// Create context with juice limit
long balance=account.getBalance();
long juiceLimit=Juice.calcAvailable(balance, juicePrice);
juiceLimit=Math.min(Constants.MAX_TRANSACTION_JUICE,juiceLimit);
long initialJuice=0;
if (juiceLimit<=initialJuice) {
return Context.create(this,origin).withJuiceError();
}
// Create context ready to execute, with at least some available juice
Context ctx = Context.create(this, origin, juiceLimit);
ctx=ctx.withJuice(initialJuice);
return ctx;
}
@Override
public boolean isCanonical() {
return true;
}
/**
* Computes the weighted stake for each peer. Adds a single entry for the null
* key, containing the total stake
*
* @return Map of Stakes
*/
public HashMap computeStakes() {
HashMap hm = new HashMap<>(peers.size());
long timeStamp=this.getTimestamp().longValue();
Double totalStake = peers.reduceEntries((acc, e) -> {
PeerStatus ps=e.getValue();
double stake = (double) (ps.getTotalStake());
long peerTimestamp=ps.getTimestamp();
double decay=Economics.stakeDecay(timeStamp,peerTimestamp);
stake*=decay;
hm.put(RT.ensureAccountKey(e.getKey()), stake);
return stake + acc;
}, 0.0);
hm.put(null, totalStake);
return hm;
}
/**
* Updates the Accounts in this State
* @param newAccounts New Accounts vector
* @return Updated State
*/
public State withAccounts(AVector newAccounts) {
if (newAccounts == accounts) return this;
return create(newAccounts, peers,globals, schedule);
}
/**
* Returns this state after updating the given account
*
* @param address Address of Account to update
* @param accountStatus New Account Status
* @return Updates State, or this state if Account was unchanged
* @throws IndexOutOfBoundsException if Address represents an illegal account position
*/
public State putAccount(Address address, AccountStatus accountStatus) {
long ix=address.longValue();
long n=accounts.count();
if (ix>n) {
throw new IndexOutOfBoundsException("Trying to add an account beyond accounts array at position: "+ix);
}
AVector newAccounts;
if (ix==n) {
// adding a new account in next position
newAccounts=accounts.conj(accountStatus);
} else {
newAccounts = accounts.assoc(ix, accountStatus);
}
return withAccounts(newAccounts);
}
/**
* Gets the AccountStatus for a given account, or null if not found.
*
* @param target Address to look up. Must not be null
* @return The AccountStatus for the given account, or null.
*/
public AccountStatus getAccount(Address target) {
long ix=target.longValue();
if ((ix<0)||(ix>=accounts.count())) return null;
return accounts.get(ix);
}
/**
* Gets the environment for a given account, or null if not found.
*
* @param addr Address of account to obtain
* @return The environment of the given account, or null if not found.
*/
public AMap getEnvironment(Address addr) {
AccountStatus as = getAccount(addr);
if (as == null) return null;
return as.getEnvironment();
}
/**
* Updates the Peers in this State
* @param newPeers New Peer Map
* @return Updated State
*/
public State withPeers(Index newPeers) {
if (peers == newPeers) return this;
return create(accounts, newPeers, globals, schedule);
}
@Override
public byte getTag() {
return Tag.STATE;
}
/**
* Adds a new Actor Account. The actor will be the last Account in the resulting State, and will
* have a default empty Actor environment.
*
* @return The updated state with the Actor Account added.
*/
public State addActor() {
AccountStatus as = AccountStatus.createActor();
AVector newAccounts = accounts.conj(as);
return withAccounts(newAccounts);
}
/**
* Compute the total funds existing within this state.
*
* Should be constant! 1,000,000,000,000,000,000 in full deployment mode
*
* @return The total value of all funds
*/
public long computeTotalFunds() {
long total = accounts.reduce((Long acc,AccountStatus as) -> acc + as.getBalance(), (Long)0L);
total += peers.reduceValues((Long acc, PeerStatus ps) -> acc + ps.getBalance(), 0L);
total += getGlobalFees().longValue();
total += getGlobalMemoryValue().longValue();
return total;
}
/**
* Compute the total memory allowance, including the memory pool.
*
* @return The total amount of CVM memory available
*/
public long computeTotalMemory() {
long total = accounts.reduce((Long acc,AccountStatus as) -> acc + as.getMemory(), (Long)0L);
total+=getGlobalMemoryPool().longValue();
return total;
}
@Override
public void validate() throws InvalidDataException {
super.validate();
}
@Override
public void validateCell() throws InvalidDataException {
accounts.validateCell();
peers.validateCell();
globals.validateCell();
schedule.validateCell();
}
/**
* Gets the current global timestamp from this state.
*
* @return The timestamp from this state.
*/
public CVMLong getTimestamp() {
return (CVMLong) globals.get(GLOBAL_TIMESTAMP);
}
/**
* Gets the current Juice price
*
* @return Juice Price
*/
public CVMLong getJuicePrice() {
return (CVMLong) globals.get(GLOBAL_JUICE_PRICE);
}
/**
* Schedules an operation with the given timestamp and Op in this state
*
* @param time Timestamp at which to execute the scheduled op
*
* @param address AccountAddress to schedule op for
* @param op Op to execute in schedule
* @return The updated State
*/
public State scheduleOp(long time, Address address, AOp> op) {
AVector v = Vectors.of(address, op);
LongBlob key = LongBlob.create(time);
AVector list = schedule.get(key);
if (list == null) {
list = Vectors.of(v);
} else {
list = list.append(v);
}
Index> newSchedule = schedule.assoc(key, list);
return this.withSchedule(newSchedule);
}
/**
* Gets the current schedule data structure for this state
*
* @return The schedule data structure.
*/
public Index> getSchedule() {
return schedule;
}
/**
* Gets the Global Fees accumulated in the State
* @return Global Fees
*/
public CVMLong getGlobalFees() {
return (CVMLong) globals.get(GLOBAL_FEES);
}
/**
* Update Global Fees
* @param newFees New Fees
* @return Updated State
*/
public State withGlobalFees(CVMLong newFees) {
return withGlobals(globals.assoc(GLOBAL_FEES,newFees));
}
/**
* Gets the PeerStatus record for the given Address, or null if it does not
* exist
*
* @param peerAddress Address of Peer to check
* @return PeerStatus
*/
public PeerStatus getPeer(AccountKey peerAddress) {
return getPeers().get(peerAddress);
}
/**
* Updates the specified peer status
*
* @param peerKey Peer Key
* @param updatedPeer New Peer Status
* @return Updated state
*/
public State withPeer(AccountKey peerKey, PeerStatus updatedPeer) {
return withPeers(peers.assoc(peerKey, updatedPeer));
}
/**
* Gets the next available address for allocation, i.e. the lowest Address
* that does not yet exist in this State.
*
* @return Next address available
*/
public Address nextAddress() {
return Address.create(accounts.count());
}
/**
* Look up an Address from CNS
* @param name CNS name String
* @return Address from CNS, or null if not found
*/
public Address lookupCNS(String name) {
Context ctx=Context.create(this);
return (Address) ctx.lookupCNS(name).getResult();
}
/**
* Gets globals.
*
* @return Vector of global values
*/
public AVector getGlobals() {
return globals;
}
/**
* Updates the State with a new timestamp
* @param timestamp New timestamp
* @return Updated State
*/
public State withTimestamp(long timestamp) {
return withGlobals(globals.assoc(GLOBAL_TIMESTAMP, CVMLong.create(timestamp)));
}
@Override
public boolean equals(ACell a) {
if (!(a instanceof State)) return false;
State as=(State)a;
return equals(as);
}
/**
* Tests if this State is equal to another
* @param a State to compare with
* @return true if equal, false otherwise
*/
public boolean equals(State a) {
if (this == a) return true; // important optimisation for e.g. hashmap equality
if (a == null) return false;
Hash h=this.cachedHash();
if (h!=null) {
Hash ha=a.cachedHash();
if (ha!=null) return Cells.equals(h, ha);
}
if (!(Cells.equals(accounts, a.accounts))) return false;
if (!(Cells.equals(globals, a.globals))) return false;
if (!(Cells.equals(peers, a.peers))) return false;
if (!(Cells.equals(schedule, a.schedule))) return false;
return true;
}
@Override
public RecordFormat getFormat() {
return FORMAT;
}
public CVMLong getGlobalMemoryValue() {
return (CVMLong)(globals.get(GLOBAL_MEMORY_CVX));
}
public CVMLong getGlobalMemoryPool() {
return (CVMLong)(globals.get(GLOBAL_MEMORY_MEM));
}
public double getMemoryPrice() {
long pool=getGlobalMemoryPool().longValue();
long value=getGlobalMemoryValue().longValue();
return ((double)value)/pool;
}
public State updateMemoryPool(long cvx, long mem) {
AVector r=globals;
r=r.assoc(GLOBAL_MEMORY_CVX, CVMLong.create(cvx));
r=r.assoc(GLOBAL_MEMORY_MEM, CVMLong.create(mem));
return withGlobals(r);
}
public boolean hasAccount(Address address) {
long av=address.longValue();
return (av>=0) &&(av statesAsOfRange(AVector states, CVMLong timestamp, long interval, int count) {
AVector v = Vectors.empty();
for (int i = 0; i < count; i++) {
v = v.conj(stateAsOf(states, timestamp));
timestamp = CVMLong.create(timestamp.longValue() + interval);
}
return v;
}
public static State stateAsOf(AVector states, CVMLong timestamp) {
return Utils.binarySearchLeftmost(states, State::getTimestamp, Comparator.comparingLong(CVMLong::longValue), timestamp);
}
}