convex.core.Order 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 convex.core.data.ACell;
import convex.core.data.ARecord;
import convex.core.data.AVector;
import convex.core.data.Blob;
import convex.core.data.Format;
import convex.core.data.IRefFunction;
import convex.core.data.Keyword;
import convex.core.data.Keywords;
import convex.core.data.Ref;
import convex.core.data.SignedData;
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.RecordFormat;
/**
* Class representing an Ordering of transactions, along with the consensus position.
*
* An Ordering contains:
*
* - The Vector of known verified Blocks announced by the Peer
* - The proposed consensus point (point at which the peer believes there is sufficient
* alignment for consensus)
* - The current consensus point (point at which the
* peer has observed sufficient consistent consensus proposals)
*
*
* An Ordering is immutable.
*
*/
public class Order extends ARecord {
/**
* Ref to Blocks Vector. We use Ref to assist de-duplication, since many Orders
* likely to share same Blocks value
*/
private final Ref>> blocks;
/**
* Array of consensus points for each consensus level. The first element (block count)
* is ignored.
*/
private final long [] consensusPoints;
/**
* Timestamp of this Order
*/
private final long timestamp;
private static final Keyword[] KEYS = new Keyword[] { Keywords.BLOCKS, Keywords.CONSENSUS_POINT, Keywords.PROPOSAL_POINT , Keywords.TIMESTAMP};
private static final RecordFormat FORMAT = RecordFormat.of(KEYS);
private static final long[] EMPTY_CONSENSUS_ARRAY = new long[Constants.CONSENSUS_LEVELS];
private Order(Ref>> blocks, long[] consensusPoints, long timestamp) {
super(FORMAT.count());
this.blocks = blocks;
this.consensusPoints=consensusPoints;
this.timestamp = timestamp;
}
/**
* Create an Order
* @param blocks Blocks in Order
* @param proposalPoint Proposal Point
* @param consensusPoint Consensus Point
* @return New Order instance
*/
private static Order create(Ref>> blocks, long proposalPoint, long consensusPoint, long timestamp) {
long[] consensusPoints=new long[Constants.CONSENSUS_LEVELS];
consensusPoints[0] = blocks.getValue().count();
consensusPoints[1] = proposalPoint;
consensusPoints[2] = consensusPoint;
return new Order(blocks, consensusPoints,timestamp);
}
/**
* Create an Order with the given consensus positions and Blocks. Mainly for testing.
* @param proposalPoint Proposal Point
* @param consensusPoint Consensus Point
* @param blocks Blocks in Order
* @return New Order instance
*/
@SuppressWarnings("unchecked")
public static Order create(long proposalPoint, long consensusPoint, SignedData... blocks ) {
return create(Vectors.of((Object[])blocks).getRef(), proposalPoint, consensusPoint,0);
}
/**
* Create an empty Order
* @return New Order instance
*/
public static Order create() {
return new Order(Vectors.empty().getRef(), EMPTY_CONSENSUS_ARRAY,0);
}
private byte getRecordTag() {
return Tag.ORDER;
}
@Override
public int encode(byte[] bs, int pos) {
bs[pos++]=getRecordTag();
return encodeRaw(bs,pos);
}
@Override
public int encodeRaw(byte[] bs, int pos) {
pos = blocks.encode(bs,pos);
for (int level=1; level>> blocks = Format.readRef(b,epos);
if (blocks==null) {
throw new BadFormatException("Null blocks in Order!");
}
epos+=blocks.getEncodingLength();
long[] cps=new long[Constants.CONSENSUS_LEVELS];
long last=Long.MAX_VALUE;
for (int level=1; levellast) {
throw new BadFormatException("Consensus point ["+pp+"] before previous value [" + last+"] at level "+level);
}
last=pp;
}
long ts = Format.readVLCLong(b,epos); // TODO: should just be 8 bytes?
epos+=Format.getVLCLength(ts);
Order result=new Order(blocks, cps,ts);
result.attachEncoding(b.slice(pos, epos));
return result;
}
@Override public final boolean isCVMValue() {
// Orders exist outside CVM only
return false;
}
/**
* Checks if another Order is consistent with this Order.
*
* Order is defined as consistent iff:
*
* - Blocks are equal up to the Consensus
* Point of this Order
*
*
*
* @param bc Order to compare with
* @return True if chains are consistent, false otherwise.
*/
public boolean checkConsistent(Order bc) {
long commonPrefix = getBlocks().commonPrefixLength(bc.getBlocks());
return commonPrefix >= getConsensusPoint();
}
/**
* Gets the Consensus Point of this Order for the specified level
* @param level Consensus level
* @return Consensus Point
*/
public long getConsensusPoint(int level) {
if (level==0) return blocks.getValue().count();
return consensusPoints[level];
}
public long[] getConsensusPoints() {
long[] result=consensusPoints.clone();
result[0]=getBlockCount();
return result;
}
/**
* Gets the Proposal Point of this Order
* @return Proposal Point
*/
public long getProposalPoint() {
return consensusPoints[Constants.CONSENSUS_LEVEL_PROPOSAL];
}
/**
* Gets the Consensus Point of this Order
* @return Consensus Point
*/
public long getConsensusPoint() {
return consensusPoints[Constants.CONSENSUS_LEVEL_CONSENSUS];
}
/**
* Gets the timestamp of this Order
* @return Proposal Point
*/
public long getTimestamp() {
return timestamp;
}
/**
* Gets the Blocks in this Order
* @return Vector of Blocks
*/
public AVector> getBlocks() {
return blocks.getValue();
}
/**
* Get a specific Block in this Order
* @param i Index of Block
* @return Block at specified index.
*/
public SignedData getBlock(long i) {
return getBlocks().get(i);
}
/**
* Append a new block of transactions in this Order
*
* @param block Block to append
* @return The updated chain
*/
public Order append(SignedData block) {
AVector> newBlocks = getBlocks().append(block);
return withBlocks(newBlocks);
}
/**
* Updates blocks in this Order. Returns the same Order if the blocks are identical.
* @param newBlocks New blocks to use
* @return Updated Order, or the same order if unchanged
*/
public Order withBlocks(AVector> newBlocks) {
if (blocks.getValue() == newBlocks) return this;
// Update proposal point and consensus point if necessary to ensure consistency
long nblocks=newBlocks.count();
long newProposalPoint = Math.min(nblocks, getProposalPoint());
long newConsensusPoint = Math.min(nblocks, getConsensusPoint());
return create(newBlocks.getRef(), newProposalPoint, newConsensusPoint, timestamp);
}
/**
* Updates timestamp in this Order. Returns the same Order if timestamp is identical.
* @param newTimestamp New timestamp to use
* @return Updated Order, or the same Order if unchanged
*/
public Order withTimestamp(long newTimestamp) {
if (timestamp == newTimestamp) return this;
return new Order(blocks, consensusPoints, newTimestamp);
}
/**
* Updates this Order with a new consensus position.
*
* @param level Consensus level to update
* @param newPosition New consensus point
* @return Updated Order, or this Order instance if no change.
*/
public Order withConsensusPoint(int level,long newPosition) {
if (level==0) return this; // TODO: sane or not?
if (consensusPoints[level]==newPosition) return this;
long[] cps=consensusPoints.clone();
cps[0]=getBlockCount();
cps[level]=newPosition;
return new Order(blocks,cps,timestamp);
}
/**
* Updates this Order with new consensus positios.
*
* @param newPositions New consensus points
* @return Updated Order, or this Order instance if no change.
*/
public Order withConsensusPoints(long[] newPositions) {
long[] cps=newPositions.clone();
cps[0]=getBlockCount();
return new Order(blocks,cps,timestamp);
}
/**
* Get the number of Blocks in this Order
* @return Number of Blocks
*/
public long getBlockCount() {
return getBlocks().count();
}
/**
* Clears the consensus and proposal point
* @return Updated order with zeroed consensus positions
*/
public Order withoutConsenus() {
return new Order(blocks, EMPTY_CONSENSUS_ARRAY,timestamp);
}
@Override
public void validate() throws InvalidDataException {
super.validate();
blocks.validate();
}
@Override
public void validateCell() throws InvalidDataException {
}
@Override
public int getRefCount() {
return 1;
}
@SuppressWarnings("unchecked")
@Override
public Ref getRef(int i) {
if (i==0) return (Ref) blocks;
throw new IndexOutOfBoundsException(i);
}
@Override
public byte getTag() {
return Tag.ORDER;
}
@Override
public ACell get(Keyword key) {
if (Keywords.BLOCKS.equals(key)) return getBlocks();
if (Keywords.CONSENSUS_POINT.equals(key)) return CVMLong.create(getConsensusPoint());
if (Keywords.PROPOSAL_POINT.equals(key)) return CVMLong.create(getProposalPoint());
if (Keywords.TIMESTAMP.equals(key)) return CVMLong.create(timestamp);
return null;
}
@SuppressWarnings("unchecked")
@Override
public Order updateRefs(IRefFunction func) {
Ref>> newBlocks = (Ref>>) func.apply(blocks);
if (blocks == newBlocks) {
return this;
}
return new Order(newBlocks, consensusPoints, timestamp);
}
/**
* Tests if this Order is equivalent to another in terms of consensus (timestamp ignored)
* @param b Order to compare with
* @return True if Orders are functionally equal, false otherwise
*/
public boolean consensusEquals(Order b) {
if (b==null) return false; // definitely not equal
for (int i=1; i