diff --git a/convex-core/src/main/java/convex/core/Result.java b/convex-core/src/main/java/convex/core/Result.java index 0b75ae39b..b9ae37f6c 100644 --- a/convex-core/src/main/java/convex/core/Result.java +++ b/convex-core/src/main/java/convex/core/Result.java @@ -283,13 +283,6 @@ private static String checkValues(AVector values) { return null; } - @Override - public int encode(byte[] bs, int pos) { - bs[pos++]=CVMTag.RESULT; - pos=values.encodeRaw(bs,pos); - return pos; - } - /** * Reads a Result from a Blob encoding. Assumes tag byte already checked. * @@ -306,10 +299,8 @@ public static Result read(Blob b, int pos) throws BadFormatException { // we can't check values yet because might be missing data - Blob enc=v.getEncoding(); Result r=buildFromVector(v); - v.attachEncoding(null); // This is an invalid encoding for vector, see above - r.attachEncoding(enc); + r.attachEncoding(b.slice(pos, epos)); return r; } @@ -389,11 +380,6 @@ public Result withID(ACell id) { return withValues(values.assoc(ID_POS, id)); } - @Override - public RecordFormat getFormat() { - return RESULT_FORMAT; - } - /** * Constructs a result from a caught exception * @param e Exception caught diff --git a/convex-core/src/main/java/convex/core/cpos/Belief.java b/convex-core/src/main/java/convex/core/cpos/Belief.java index a6c4afdee..2ccd3ea25 100644 --- a/convex-core/src/main/java/convex/core/cpos/Belief.java +++ b/convex-core/src/main/java/convex/core/cpos/Belief.java @@ -148,8 +148,6 @@ public static Belief read(Blob b, int pos) throws BadFormatException { if (values.count()!=1) throw new BadFormatException("Wrong number of values for Belief"); Belief result=new Belief(values); - - values.attachEncoding(null); result.attachEncoding(b.slice(pos,epos)); return result; } @@ -209,11 +207,6 @@ public HashMap> getOrdersHashMap() { return getOrders().toHashMap(); } - @Override - public RecordFormat getFormat() { - return BELIEF_FORMAT; - } - /** * Extract a collection of Orders from a Cell, suitable for Belief merge * @param payload Cell to extra orders from diff --git a/convex-core/src/main/java/convex/core/cpos/CPoSConstants.java b/convex-core/src/main/java/convex/core/cpos/CPoSConstants.java index 065add240..105bdbd46 100644 --- a/convex-core/src/main/java/convex/core/cpos/CPoSConstants.java +++ b/convex-core/src/main/java/convex/core/cpos/CPoSConstants.java @@ -56,6 +56,11 @@ public class CPoSConstants { * Memory allowance for genesis user / peer accounts */ public static final long INITIAL_ACCOUNT_ALLOWANCE = 1000000; + + /** + * Maximum allowed encoded peer message length in bytes (50mb) + */ + public static final long MAX_MESSAGE_LENGTH = 50000000; } diff --git a/convex-core/src/main/java/convex/core/cvm/ARecordGeneric.java b/convex-core/src/main/java/convex/core/cvm/ARecordGeneric.java index ad04d9187..1c679fa72 100644 --- a/convex-core/src/main/java/convex/core/cvm/ARecordGeneric.java +++ b/convex-core/src/main/java/convex/core/cvm/ARecordGeneric.java @@ -42,12 +42,22 @@ public ACell get(Keyword key) { } @Override - public int getRefCount() { + public final RecordFormat getFormat() { + return format; + } + + @Override + public final int getRefCount() { return values.getRefCount(); } @Override - public int encode(byte[] bs, int pos) { + public int estimatedEncodingSize() { + return values.estimatedEncodingSize(); + } + + @Override + public final int encode(byte[] bs, int pos) { bs[pos++]=tag; return encodeRaw(bs,pos); } @@ -57,7 +67,7 @@ public int encode(byte[] bs, int pos) { * @param bs Array to write to */ @Override - public int encodeRaw(byte[] bs, int pos) { + public final int encodeRaw(byte[] bs, int pos) { return values.encodeRaw(bs, pos); } @@ -83,13 +93,14 @@ protected boolean equals(ARecordGeneric a) { } @Override - public Ref getRef(int index) { + public final Ref getRef(int index) { return values.getRef(index); } @Override - public ARecordGeneric updateRefs(IRefFunction func) { + public final ARecordGeneric updateRefs(IRefFunction func) { AVector newValues=values.updateRefs(func); // ensure values are canonical via toVector + if (newValues==values) return this; return withValues(newValues); } diff --git a/convex-core/src/main/java/convex/core/cvm/CVMTag.java b/convex-core/src/main/java/convex/core/cvm/CVMTag.java index e16e45249..22c691838 100644 --- a/convex-core/src/main/java/convex/core/cvm/CVMTag.java +++ b/convex-core/src/main/java/convex/core/cvm/CVMTag.java @@ -11,7 +11,6 @@ public class CVMTag { * Tag for Convex Address type */ public static final byte ADDRESS = (byte) 0xEA; - public static final byte SYNTAX = (byte) 0x88; // ========================================== @@ -78,7 +77,6 @@ public class CVMTag { // CVM Code public static final byte OP = (byte) 0xC0; - public static final byte FN = (byte) 0xCF; public static final byte FN_MULTI = (byte) 0xCB; diff --git a/convex-core/src/main/java/convex/core/cvm/Keywords.java b/convex-core/src/main/java/convex/core/cvm/Keywords.java index 9b3efa317..d6f42b642 100644 --- a/convex-core/src/main/java/convex/core/cvm/Keywords.java +++ b/convex-core/src/main/java/convex/core/cvm/Keywords.java @@ -121,6 +121,8 @@ public class Keywords { public static final Keyword OFFER = Keyword.intern("offer"); public static final Keyword ORIGIN = Keyword.intern("origin"); public static final Keyword TARGET = Keyword.intern("target"); + public static final Keyword ARGS = Keyword.intern("args"); + public static final Keyword BLOCKS = Keyword.intern("blocks"); public static final Keyword CONSENSUS_POINT = Keyword.intern("consensus-point"); diff --git a/convex-core/src/main/java/convex/core/cvm/State.java b/convex-core/src/main/java/convex/core/cvm/State.java index ad7279516..b541e6452 100644 --- a/convex-core/src/main/java/convex/core/cvm/State.java +++ b/convex-core/src/main/java/convex/core/cvm/State.java @@ -891,11 +891,6 @@ public boolean equals(State a) { return values.equals(a.values); } - @Override - public RecordFormat getFormat() { - return FORMAT; - } - /** * Gets the Convex Coin value in the memory exchange pool * @return Memory pool Convex Coin amount (coppers) diff --git a/convex-core/src/main/java/convex/core/cvm/transactions/ATransaction.java b/convex-core/src/main/java/convex/core/cvm/transactions/ATransaction.java index ea7b8e570..07d28f418 100644 --- a/convex-core/src/main/java/convex/core/cvm/transactions/ATransaction.java +++ b/convex-core/src/main/java/convex/core/cvm/transactions/ATransaction.java @@ -1,13 +1,17 @@ package convex.core.cvm.transactions; -import convex.core.cvm.ACVMRecord; +import convex.core.cvm.ARecordGeneric; import convex.core.cvm.Address; import convex.core.cvm.Context; +import convex.core.cvm.Keywords; +import convex.core.cvm.RecordFormat; import convex.core.data.ACell; +import convex.core.data.AVector; import convex.core.data.Cells; -import convex.core.data.Format; +import convex.core.data.Keyword; import convex.core.data.type.AType; import convex.core.data.type.Transaction; +import convex.core.lang.RT; /** * Abstract base class for immutable transactions @@ -20,7 +24,7 @@ * indicating a code error or system integrity issue. * */ -public abstract class ATransaction extends ACVMRecord { +public abstract class ATransaction extends ARecordGeneric { /** * Sequence number for transactions where required sequence is currently unknown @@ -30,29 +34,13 @@ public abstract class ATransaction extends ACVMRecord { protected final Address origin; protected final long sequence; - protected ATransaction(byte tag,long count,Address origin, long sequence) { - super(tag,count); + protected ATransaction(byte tag,RecordFormat format, AVector values) { + super(tag,format,values); + this.origin=RT.ensureAddress(values.get(0)); if (origin==null) throw new IllegalArgumentException("Null Origin Address for transaction"); - this.origin=origin; - this.sequence = sequence; + this.sequence = RT.ensureLong(values.get(1)).longValue(); } - /** - * Writes this transaction to a byte array, including the message tag - */ - @Override - public abstract int encode(byte[] bs, int pos); - - @Override - public int encodeRaw(byte[] bs, int pos) { - pos = Format.writeVLQCount(bs,pos, origin.longValue()); - pos = Format.writeVLQCount(bs,pos, sequence); - return pos; - } - - @Override - public abstract int estimatedEncodingSize(); - /** * Applies the functional effect of this transaction to the current state. * @@ -94,6 +82,13 @@ public AType getType() { return Transaction.INSTANCE; } + @Override + public ACell get(Keyword key) { + if (Keywords.ORIGIN.equals(key)) return origin; + if (Keywords.SEQUENCE.equals(key)) return values.get(1); + return null; + } + /** * Updates this transaction with the specified sequence number * @param newSequence New sequence number diff --git a/convex-core/src/main/java/convex/core/cvm/transactions/Call.java b/convex-core/src/main/java/convex/core/cvm/transactions/Call.java index 7d1cb75de..e7705f587 100644 --- a/convex-core/src/main/java/convex/core/cvm/transactions/Call.java +++ b/convex-core/src/main/java/convex/core/cvm/transactions/Call.java @@ -1,6 +1,7 @@ package convex.core.cvm.transactions; import convex.core.Coin; +import convex.core.cvm.ARecordGeneric; import convex.core.cvm.Address; import convex.core.cvm.CVMTag; import convex.core.cvm.Context; @@ -9,15 +10,14 @@ import convex.core.data.ACell; import convex.core.data.AVector; import convex.core.data.Blob; -import convex.core.data.Cells; -import convex.core.data.Format; -import convex.core.data.IRefFunction; import convex.core.data.Keyword; -import convex.core.data.Ref; import convex.core.data.Symbol; +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.RT; +import convex.core.util.ErrorMessages; /** * Transaction representing a Call to an Actor. @@ -30,46 +30,43 @@ public class Call extends ATransaction { public static final long DEFAULT_OFFER = 0; - protected final Address target; + protected final ACell target; protected final long offer; protected final Symbol functionName; - protected final AVector args; + protected AVector args; - private static final Keyword[] KEYS = new Keyword[] { Keywords.ORIGIN, Keywords.SEQUENCE, Keywords.TARGET , Keywords.OFFER,Keywords.CALL }; + private static final Keyword[] KEYS = new Keyword[] { Keywords.ORIGIN, Keywords.SEQUENCE, Keywords.TARGET , Keywords.OFFER,Keywords.CALL, Keywords.ARGS }; private static final RecordFormat FORMAT = RecordFormat.of(KEYS); - protected Call(Address address, long sequence, Address target, long offer,Symbol functionName,AVector args) { - super(CVMTag.CALL,FORMAT.count(),address,sequence); + protected Call(AVector values) { + super(CVMTag.CALL,FORMAT,values); + this.target=values.get(2); + this.offer=RT.ensureLong(values.get(3)).longValue(); + this.functionName=RT.ensureSymbol(values.get(4)); + // no need to set args, will be pulled on demand + } + + protected Call(Address origin, long sequence, ACell target, long offer,Symbol functionName,AVector args) { + super(CVMTag.CALL,FORMAT,Vectors.create(origin,CVMLong.create(sequence),target, CVMLong.create(offer),functionName,args)); this.target=target; this.functionName=functionName; this.offer=offer; this.args=args; } - public static Call create(Address address, long sequence, Address target, long offer,Symbol functionName,AVector args) { + public static Call create(Address address, long sequence, ACell target, long offer,Symbol functionName,AVector args) { return new Call(address,sequence,target,offer,functionName,args); } - public static Call create(Address address, long sequence, Address target, Symbol functionName,AVector args) { + public static Call create(Address address, long sequence, ACell target, Symbol functionName,AVector args) { return create(address,sequence,target,DEFAULT_OFFER,functionName,args); } - @Override - public int encode(byte[] bs, int pos) { - bs[pos++] = CVMTag.CALL; - return encodeRaw(bs,pos); + public static Call create(AVector values) { + return new Call(values); } - @Override - public int encodeRaw(byte[] bs, int pos) { - pos = super.encodeRaw(bs,pos); // sequence - pos = Format.write(bs,pos, target); - pos=Format.writeVLQCount(bs,pos, offer); - pos=Format.write(bs,pos, functionName); - pos=Format.write(bs,pos, args); - return pos; - } /** * Reads a Call Transaction from a Blob encoding @@ -79,29 +76,12 @@ public int encodeRaw(byte[] bs, int pos) { * @throws BadFormatException In the event of any encoding error */ public static Call read(Blob b, int pos) throws BadFormatException { - int epos=pos+1; // skip tag - long aval=Format.readVLQCount(b,epos); - Address origin=Address.create(aval); - epos+=Format.getVLQCountLength(aval); - - long sequence = Format.readVLQCount(b,epos); - epos+=Format.getVLQCountLength(sequence); - - Address target=Format.read(b, epos); - epos+=Cells.getEncodingLength(target); - - long offer=Format.readVLQCount(b,epos); - epos+=Format.getVLQCountLength(offer); - if (!Coin.isValidAmount(offer)) throw new BadFormatException("Invalid offer in Call"); - - - Symbol functionName=Format.read(b,epos); - epos+=Cells.getEncodingLength(functionName); - - AVector args = Format.read(b,epos); - epos+=Cells.getEncodingLength(args); - - Call result=create(origin,sequence, target, offer, functionName,args); + AVector values=Vectors.read(b, pos); + int epos=pos+values.getEncodingLength(); + + if (values.count()!=KEYS.length) throw new BadFormatException(ErrorMessages.RECORD_VALUE_NUMBER); + + Call result=new Call(values); result.attachEncoding(b.slice(pos,epos)); return result; } @@ -121,23 +101,6 @@ public void validateCell() throws InvalidDataException { if (!Coin.isValidAmount(offer)) throw new InvalidDataException("Invalid offer",this); target.validateCell(); } - - @Override - public int getRefCount() { - return args.getRefCount(); - } - - @Override - public Ref getRef(int i) { - return args.getRef(i); - } - - @Override - public Call updateRefs(IRefFunction func) { - AVector newArgs=args.updateRefs(func); - if (args==newArgs) return this; - return new Call(origin,sequence,target,offer,functionName,newArgs); - } @Override public Call withSequence(long newSequence) { @@ -153,18 +116,24 @@ public Call withOrigin(Address newAddress) { @Override public ACell get(Keyword key) { - if (Keywords.CALL.equals(key)) return args.cons(functionName); - if (Keywords.OFFER.equals(key)) return CVMLong.create(offer); - if (Keywords.ORIGIN.equals(key)) return origin; - if (Keywords.SEQUENCE.equals(key)) return CVMLong.create(sequence); + if (Keywords.CALL.equals(key)) return functionName; + if (Keywords.OFFER.equals(key)) return values.get(3); if (Keywords.TARGET.equals(key)) return target; + if (Keywords.ARGS.equals(key)) return getArgs(); + return super.get(key); // covers origin and sequence + } - return null; + private ACell getArgs() { + if (args==null) { + args=RT.ensureVector(values.get(5)); + } + return args; } @Override - public RecordFormat getFormat() { - return FORMAT; + protected ARecordGeneric withValues(AVector newValues) { + if (values==newValues) return this; + return new Call(values); } diff --git a/convex-core/src/main/java/convex/core/cvm/transactions/Invoke.java b/convex-core/src/main/java/convex/core/cvm/transactions/Invoke.java index 61c114cfa..6e9a43896 100644 --- a/convex-core/src/main/java/convex/core/cvm/transactions/Invoke.java +++ b/convex-core/src/main/java/convex/core/cvm/transactions/Invoke.java @@ -1,22 +1,24 @@ package convex.core.cvm.transactions; import convex.core.cvm.AOp; +import convex.core.cvm.ARecordGeneric; import convex.core.cvm.Address; import convex.core.cvm.CVMTag; import convex.core.cvm.Context; import convex.core.cvm.Keywords; import convex.core.cvm.RecordFormat; import convex.core.data.ACell; +import convex.core.data.AVector; import convex.core.data.Blob; import convex.core.data.Cells; import convex.core.data.Format; -import convex.core.data.IRefFunction; import convex.core.data.Keyword; -import convex.core.data.Ref; +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.Reader; +import convex.core.util.ErrorMessages; /** * Transaction class representing the Invoke of an on-chain operation. @@ -31,20 +33,24 @@ * performed outside the CVM which not provide a parser internally. */ public class Invoke extends ATransaction { - protected final ACell command; + protected ACell command; private static final Keyword[] KEYS = new Keyword[] { Keywords.ORIGIN, Keywords.SEQUENCE,Keywords.COMMAND}; private static final RecordFormat FORMAT = RecordFormat.of(KEYS); private static final long FORMAT_COUNT=FORMAT.count(); - protected Invoke(Address address,long sequence, ACell args) { - super(CVMTag.INVOKE,FORMAT_COUNT,address,sequence); - this.command = args; + protected Invoke(Address origin,long sequence, ACell command) { + super(CVMTag.INVOKE,FORMAT,Vectors.create(origin,CVMLong.create(sequence),command)); + this.command = command; + } + + protected Invoke(AVector values) { + super(CVMTag.INVOKE,FORMAT,values); } - public static Invoke create(Address address,long sequence, ACell command) { + public static Invoke create(Address origin,long sequence, ACell command) { if (sequence<0) throw new IllegalArgumentException("Illegal sequence number: "+sequence); - return new Invoke(address,sequence, command); + return new Invoke(origin,sequence, command); } /** @@ -57,25 +63,13 @@ public static Invoke create(Address address,long sequence, ACell command) { public static Invoke create(Address address,long sequence, String command) { return create(address,sequence, Reader.read(command)); } - - @Override - public int encode(byte[] bs, int pos) { - bs[pos++] = CVMTag.INVOKE; - return encodeRaw(bs,pos); - } - - @Override - public int encodeRaw(byte[] bs, int pos) { - pos = super.encodeRaw(bs,pos); // origin, sequence - pos = Format.write(bs,pos, command); - return pos; - } /** * Get the command for this transaction, as code. * @return Command object. */ - public Object getCommand() { + public ACell getCommand() { + if (command==null) command=values.get(2); return command; } @@ -88,31 +82,25 @@ public Object getCommand() { * @throws BadFormatException In the event of any encoding error */ public static Invoke read(Blob b, int pos) throws BadFormatException { - int epos=pos+1; // skip tag - long aval=Format.readVLQCount(b,epos); - Address address=Address.create(aval); - epos+=Format.getVLQCountLength(aval); - - long sequence = Format.readVLQCount(b,epos); - epos+=Format.getVLQCountLength(sequence); - - ACell args=Format.read(b, epos); - epos+=Cells.getEncodingLength(args); + AVector values=Vectors.read(b, pos); + int epos=pos+values.getEncodingLength(); - Invoke result=create(address, sequence, args); - result.attachEncoding(b.slice(pos, epos)); + if (values.count()!=FORMAT_COUNT) throw new BadFormatException(ErrorMessages.RECORD_VALUE_NUMBER); + + Invoke result=new Invoke(values); + result.attachEncoding(b.slice(pos,epos)); return result; } @Override public Context apply(final Context context) { Context ctx=context; - + ACell cmd=getCommand(); // Run command - if (command instanceof AOp) { - ctx = ctx.run((AOp) command); + if (cmd instanceof AOp) { + ctx = ctx.run((AOp) cmd); } else { - ctx = ctx.run(command); + ctx = ctx.run(cmd); } return ctx; } @@ -134,23 +122,6 @@ public void validateCell() throws InvalidDataException { } } - @Override - public int getRefCount() { - return Cells.refCount(command); - } - - @Override - public Ref getRef(int i) { - return Cells.getRef(command, i); - } - - @Override - public Invoke updateRefs(IRefFunction func) { - ACell newCommand = Ref.update(command, func); - if (newCommand == command) return this; - return Invoke.create(origin,getSequence(), newCommand); - } - @Override public Invoke withSequence(long newSequence) { if (newSequence==this.sequence) return this; @@ -165,16 +136,14 @@ public Invoke withOrigin(Address newAddress) { @Override public ACell get(Keyword key) { - if (Keywords.COMMAND.equals(key)) return command; - if (Keywords.ORIGIN.equals(key)) return origin; - if (Keywords.SEQUENCE.equals(key)) return CVMLong.create(sequence); - - return null; + if (Keywords.COMMAND.equals(key)) return getCommand(); + return super.get(key); // covers origin and sequence } @Override - public RecordFormat getFormat() { - return FORMAT; + protected ARecordGeneric withValues(AVector newValues) { + if (values==newValues) return this; + return new Invoke(values); } } diff --git a/convex-core/src/main/java/convex/core/cvm/transactions/Multi.java b/convex-core/src/main/java/convex/core/cvm/transactions/Multi.java index cc11421ba..f00fc2d37 100644 --- a/convex-core/src/main/java/convex/core/cvm/transactions/Multi.java +++ b/convex-core/src/main/java/convex/core/cvm/transactions/Multi.java @@ -2,6 +2,7 @@ import convex.core.ErrorCodes; import convex.core.Result; +import convex.core.cvm.ARecordGeneric; import convex.core.cvm.AccountStatus; import convex.core.cvm.Address; import convex.core.cvm.CVMTag; @@ -11,14 +12,14 @@ import convex.core.data.ACell; 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.Ref; 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.RT; +import convex.core.util.ErrorMessages; +import convex.core.util.Utils; /** * The Multi class enables multiple child transactions to be grouped into a single @@ -32,9 +33,9 @@ */ public class Multi extends ATransaction { - protected Ref> txs; + protected AVector txs; - private int mode; + private final int mode; /** * Mode to execute and report all transactions, regardless of outcome. @@ -61,59 +62,38 @@ public class Multi extends ATransaction { public static final int MODE_UNTIL=3; - private static final Keyword[] KEYS = new Keyword[] { Keywords.ORIGIN, Keywords.SEQUENCE,Keywords.MODE,Keywords.TXS}; private static final RecordFormat FORMAT = RecordFormat.of(KEYS); - protected Multi(Address origin, long sequence, int mode, Ref> txs) { - super(CVMTag.MULTI,FORMAT.count(), origin, sequence); + protected Multi(Address origin, long sequence, int mode, AVector txs) { + super(CVMTag.MULTI,FORMAT, Vectors.create(origin,CVMLong.create(sequence),CVMLong.create(mode),txs)); this.mode=mode; this.txs=txs; } + protected Multi(AVector values) { + super(CVMTag.MULTI,FORMAT, values); + this.mode=Utils.checkedInt(RT.ensureLong(values.get(2)).longValue()); + if (!isValidMode(mode)) throw new IllegalArgumentException("Bad mode"); + } + public static Multi create(Address origin, long sequence, int mode, ATransaction... txs) { AVector v= Vectors.create(txs); - return new Multi(origin,sequence,mode,v.getRef()); + return new Multi(origin,sequence,mode,v); } public int getMode() { return mode; } - - @Override - public int encode(byte[] bs, int pos) { - bs[pos++] = CVMTag.MULTI; - return encodeRaw(bs,pos); - } - - @Override - public int encodeRaw(byte[] bs, int pos) { - pos = super.encodeRaw(bs,pos); // origin, sequence - pos = Format.writeVLQCount(bs,pos, mode); - pos = txs.encode(bs, pos); - return pos; - } - public static Multi read(Blob b, int pos) throws BadFormatException { - int epos=pos+1; // skip tag - - long aval=Format.readVLQCount(b,epos); - Address origin=Address.create(aval); - epos+=Format.getVLQCountLength(aval); - - long sequence = Format.readVLQCount(b,epos); - epos+=Format.getVLQCountLength(sequence); + AVector values=Vectors.read(b, pos); + int epos=pos+values.getEncodingLength(); - long mode = Format.readVLQCount(b,epos); - if (!isValidMode(mode)) throw new BadFormatException("Invalid Multi transaction mode: "+mode); - epos+=Format.getVLQCountLength(mode); - - Ref> txs=Format.readRef(b, epos); - epos+=txs.getEncodingLength(); - - Multi result=new Multi(origin,sequence,(int)mode,txs); + if (values.count()!=KEYS.length) throw new BadFormatException(ErrorMessages.RECORD_VALUE_NUMBER); + + Multi result=new Multi(values); result.attachEncoding(b.slice(pos,epos)); return result; } @@ -122,17 +102,14 @@ private static boolean isValidMode(long mode) { return (mode>=MODE_ANY)&&(mode<=MODE_UNTIL); } - @Override - public int estimatedEncodingSize() { - return 30+Format.MAX_EMBEDDED_LENGTH; - } + @Override public Context apply(Context ctx) { // save initial context, we might need this for rollback Context ictx=ctx.fork(); - AVector ts=txs.getValue(); + AVector ts=getTransactions(); // Context initialContext=ctx.fork(); long n=ts.count(); AVector rs=Vectors.empty(); @@ -163,6 +140,13 @@ public Context apply(Context ctx) { return rctx; } + private AVector getTransactions() { + if (txs==null) { + txs=RT.ensureVector(values.get(3)); + } + return txs; + } + private Context applySubTransaction(Context ctx, ATransaction t) { Address torigin=t.origin; if (!this.origin.equals(torigin)) { @@ -201,37 +185,15 @@ public void validateCell() throws InvalidDataException { @Override public ACell get(Keyword key) { - if (Keywords.ORIGIN.equals(key)) return origin; - if (Keywords.SEQUENCE.equals(key)) return CVMLong.create(sequence); if (Keywords.MODE.equals(key)) return CVMLong.create(mode); - if (Keywords.TXS.equals(key)) return txs.getValue(); - return null; - } - - @Override - public int getRefCount() { - // Always just one Ref - return 1; - } - - @SuppressWarnings("unchecked") - @Override - public Ref getRef(int i) { - if (i==0) return (Ref) txs; - throw new IndexOutOfBoundsException(i); - } - - @SuppressWarnings("unchecked") - @Override - public Multi updateRefs(IRefFunction func) { - Ref> ntxs=(Ref>) func.apply(txs); - if (ntxs==txs) return this; - return new Multi(origin,sequence,mode,ntxs); + if (Keywords.TXS.equals(key)) return getTransactions(); + return super.get(key); // covers origin and sequence } @Override - public RecordFormat getFormat() { - return FORMAT; + protected ARecordGeneric withValues(AVector newValues) { + if (values==newValues) return this; + return new Multi(values); } diff --git a/convex-core/src/main/java/convex/core/cvm/transactions/Transfer.java b/convex-core/src/main/java/convex/core/cvm/transactions/Transfer.java index d07086c4f..b5623f6c9 100644 --- a/convex-core/src/main/java/convex/core/cvm/transactions/Transfer.java +++ b/convex-core/src/main/java/convex/core/cvm/transactions/Transfer.java @@ -1,6 +1,7 @@ package convex.core.cvm.transactions; import convex.core.Coin; +import convex.core.cvm.ARecordGeneric; import convex.core.cvm.Address; import convex.core.cvm.CVMTag; import convex.core.cvm.Context; @@ -8,14 +9,16 @@ import convex.core.cvm.Keywords; import convex.core.cvm.RecordFormat; import convex.core.data.ACell; +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.Ref; +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.RT; +import convex.core.util.ErrorMessages; /** * Transaction class representing a coin Transfer from one account to another @@ -28,49 +31,30 @@ public class Transfer extends ATransaction { private static final RecordFormat FORMAT = RecordFormat.of(KEYS); protected Transfer(Address origin,long sequence, Address target, long amount) { - super(CVMTag.TRANSFER,FORMAT.count(),origin,sequence); + super(CVMTag.TRANSFER,FORMAT,Vectors.create(origin,CVMLong.create(sequence),target,CVMLong.create(amount))); this.target = target; this.amount = amount; } + + protected Transfer(AVector values) { + super(CVMTag.TRANSFER,FORMAT,values); + this.target = RT.ensureAddress(values.get(2)); + this.amount = RT.ensureLong(values.get(3)).longValue(); + } public static Transfer create(Address origin,long sequence, Address target, long amount) { + if (!Coin.isValidAmount(amount)) throw new IllegalArgumentException(ErrorMessages.BAD_AMOUNT); return new Transfer(origin,sequence, target, amount); } - - @Override - public int encode(byte[] bs, int pos) { - bs[pos++]=CVMTag.TRANSFER; - return encodeRaw(bs,pos); - } - - @Override - public int encodeRaw(byte[] bs, int pos) { - pos = super.encodeRaw(bs,pos); // origin, sequence - pos = Format.writeVLQCount(bs, pos, target.longValue()); - pos = Format.writeVLQCount(bs, pos, amount); - return pos; - } - public static ATransaction read(Blob b, int pos) throws BadFormatException { - int epos=pos+1; // skip tag - long aval=Format.readVLQCount(b,epos); - Address origin=Address.create(aval); - epos+=Format.getVLQCountLength(aval); - - long sequence = Format.readVLQCount(b,epos); - epos+=Format.getVLQCountLength(sequence); - - long tval=Format.readVLQCount(b,epos); - Address target=Address.create(tval); - epos+=Format.getVLQCountLength(tval); + AVector values=Vectors.read(b, pos); + int epos=pos+values.getEncodingLength(); - long amount = Format.readVLQCount(b,epos); - epos+=Format.getVLQCountLength(amount); - if (!Coin.isValidAmount(amount)) throw new BadFormatException("Illegal amount in transfer: "+amount); - - Transfer result=create(origin,sequence, target, amount); - result.attachEncoding(b.slice(pos, epos)); + if (values.count()!=KEYS.length) throw new BadFormatException(ErrorMessages.RECORD_VALUE_NUMBER); + + Transfer result=new Transfer(values); + result.attachEncoding(b.slice(pos,epos)); return result; } @@ -117,22 +101,6 @@ public Address getTarget() { public long getAmount() { return amount; } - - @Override - public Ref getRef(int i) { - throw new IndexOutOfBoundsException(i); - } - - @Override - public ACell updateRefs(IRefFunction func) { - return this; - } - - @Override - public int getRefCount() { - // No Refs - return 0; - } @Override public Transfer withSequence(long newSequence) { @@ -148,17 +116,14 @@ public Transfer withOrigin(Address newAddress) { @Override public ACell get(Keyword key) { - if (Keywords.AMOUNT.equals(key)) return CVMLong.create(amount); - if (Keywords.ORIGIN.equals(key)) return origin; - if (Keywords.SEQUENCE.equals(key)) return CVMLong.create(sequence); - if (Keywords.TARGET.equals(key)) return target; - - return null; + if (Keywords.TARGET.equals(key)) return RT.ensureAddress(values.get(2)); + if (Keywords.AMOUNT.equals(key)) return RT.ensureLong(values.get(3)); + return super.get(key); // covers origin and sequence } @Override - public RecordFormat getFormat() { - return FORMAT; + protected ARecordGeneric withValues(AVector newValues) { + if (values==newValues) return this; + return new Transfer(newValues); } - } diff --git a/convex-core/src/main/java/convex/core/data/Format.java b/convex-core/src/main/java/convex/core/data/Format.java index bf2dffb44..29107c10a 100644 --- a/convex-core/src/main/java/convex/core/data/Format.java +++ b/convex-core/src/main/java/convex/core/data/Format.java @@ -12,6 +12,7 @@ import convex.core.cpos.Belief; import convex.core.cpos.Block; import convex.core.cpos.BlockResult; +import convex.core.cpos.CPoSConstants; import convex.core.cpos.Order; import convex.core.cvm.ACVMRecord; import convex.core.cvm.AFn; @@ -93,11 +94,6 @@ public class Format { */ public static final int MAX_REF_LENGTH = Math.max(Ref.INDIRECT_ENCODING_LENGTH, MAX_EMBEDDED_LENGTH); - /** - * Maximum allowed encoded message length in bytes - */ - public static final long MAX_MESSAGE_LENGTH = 20000000; - /** * Memory size of a fully embedded value (zero) */ @@ -773,7 +769,7 @@ public static int estimateEncodingSize(ACell cell) { */ public static ACell[] decodeCells(Blob data) throws BadFormatException { long ml=data.count(); - if (ml>Format.MAX_MESSAGE_LENGTH) throw new BadFormatException("Message too long: "+ml); + if (ml>CPoSConstants.MAX_MESSAGE_LENGTH) throw new BadFormatException("Message too long: "+ml); if (ml==0) return Cells.EMPTY_ARRAY; ArrayList cells=new ArrayList<>(); @@ -949,7 +945,7 @@ public static Blob encodeMultiCell(ACell a, boolean everything) { int cellLength=lengthFieldSize+encLength; int newLength=ml[0]+cellLength; - if (newLength>Format.MAX_MESSAGE_LENGTH) return; + if (newLength>CPoSConstants.MAX_MESSAGE_LENGTH) return; ml[0]=newLength; refs.add(cr); if (everything) Cells.visitBranchRefs(c, addToStackFunc); diff --git a/convex-core/src/main/java/convex/core/data/VectorLeaf.java b/convex-core/src/main/java/convex/core/data/VectorLeaf.java index f04b85547..a0f85e3a1 100644 --- a/convex-core/src/main/java/convex/core/data/VectorLeaf.java +++ b/convex-core/src/main/java/convex/core/data/VectorLeaf.java @@ -266,7 +266,8 @@ public static VectorLeaf read(long count, Blob b, int pos) } VectorLeaf result=new VectorLeaf(items, pfx, count); - result.attachEncoding(b.slice(pos, rpos)); + // Attach encoding only if "real" + if (b.byteAtUnchecked(pos)==Tag.VECTOR) result.attachEncoding(b.slice(pos, rpos)); return result; } diff --git a/convex-core/src/main/java/convex/core/data/VectorTree.java b/convex-core/src/main/java/convex/core/data/VectorTree.java index 0b43552f6..8878d9902 100644 --- a/convex-core/src/main/java/convex/core/data/VectorTree.java +++ b/convex-core/src/main/java/convex/core/data/VectorTree.java @@ -226,7 +226,8 @@ public static VectorTree read(long count, Blob b, int pos) } VectorTree result= new VectorTree(items, count); - result.attachEncoding(b.slice(pos, rpos)); + // Attach encoding only if "real" + if (b.byteAtUnchecked(pos)==Tag.VECTOR) result.attachEncoding(b.slice(pos, rpos)); return result; } diff --git a/convex-core/src/main/java/convex/core/data/Vectors.java b/convex-core/src/main/java/convex/core/data/Vectors.java index 76558448d..91784db4e 100644 --- a/convex-core/src/main/java/convex/core/data/Vectors.java +++ b/convex-core/src/main/java/convex/core/data/Vectors.java @@ -138,7 +138,9 @@ public static AVector repeat(T m, int count) { } /** - * Reads a Vector for the specified Blob. Assumes Tag byte already checked. + * Reads a Vector for the specified Blob. + * + * Assumes Tag byte already checked, attaches encoding iff tag is Tag.VECTOR. * * Distinguishes between child types according to count. * @@ -151,11 +153,14 @@ public static AVector repeat(T m, int count) { public static AVector read(Blob b, int pos) throws BadFormatException { long count = Format.readVLQCount(b,pos+1); if (count < 0) throw new BadFormatException("Negative length"); + + AVector result; if (VectorLeaf.isValidCount(count)) { - return VectorLeaf.read(count,b,pos); + result= VectorLeaf.read(count,b,pos); } else { - return VectorTree.read(count,b,pos); + result= VectorTree.read(count,b,pos); } + return result; } /** diff --git a/convex-core/src/main/java/convex/core/util/ErrorMessages.java b/convex-core/src/main/java/convex/core/util/ErrorMessages.java index d6fc71604..c6bf101a9 100644 --- a/convex-core/src/main/java/convex/core/util/ErrorMessages.java +++ b/convex-core/src/main/java/convex/core/util/ErrorMessages.java @@ -21,6 +21,8 @@ public class ErrorMessages { public static final String TODO = "Not yet implemented."; public static final String UNREACHABLE = "Should be unreachable"; + public static final String RECORD_VALUE_NUMBER = "Wrong number of record values"; + public static final String BAD_AMOUNT = "Illegal Convex Coin amount"; public static String immutable(Object a) { return "Object is immutable: "+a.getClass(); diff --git a/convex-core/src/test/java/convex/core/data/AdversarialDataTest.java b/convex-core/src/test/java/convex/core/data/AdversarialDataTest.java index 1b5080652..d7ab0ce7a 100644 --- a/convex-core/src/test/java/convex/core/data/AdversarialDataTest.java +++ b/convex-core/src/test/java/convex/core/data/AdversarialDataTest.java @@ -318,12 +318,8 @@ public void testBadTransactions() { Address HERO=Init.GENESIS_ADDRESS; // invalid amount in Transfer - invalidTest(Transfer.create(HERO, 0, HERO,Long.MAX_VALUE)); + assertThrows(IllegalArgumentException.class, ()->Transfer.create(HERO, 0, HERO,Long.MAX_VALUE)); - // invalid offer amounts in Call - invalidTest(Call.create(HERO, 0, HERO,Long.MAX_VALUE,Symbols.FOO,Vectors.empty())); - invalidTest(Call.create(HERO, 0, HERO,-10,Symbols.FOO,Vectors.empty())); - // invalid origin in Call. TODO: reconsider? assertThrows(IllegalArgumentException.class, ()->Call.create(null, 0, HERO,0,Symbols.FOO,Vectors.empty())); } diff --git a/convex-core/src/test/java/convex/store/EtchStoreTest.java b/convex-core/src/test/java/convex/store/EtchStoreTest.java index 87629f1be..138c29abe 100644 --- a/convex-core/src/test/java/convex/store/EtchStoreTest.java +++ b/convex-core/src/test/java/convex/store/EtchStoreTest.java @@ -195,8 +195,8 @@ public void testBeliefAnnounce() throws IOException { assertEquals(Ref.UNKNOWN,rt.getStatus()); assertEquals(3,Cells.refCount(t1)); - assertEquals(0,Cells.refCount(t2)); - assertEquals(15,Refs.totalRefCount(belief)); + assertEquals(4,Cells.refCount(t2)); + assertEquals(22,Refs.totalRefCount(belief)); Consumer> noveltyHandler=r-> { diff --git a/convex-peer/src/main/java/convex/peer/BeliefPropagator.java b/convex-peer/src/main/java/convex/peer/BeliefPropagator.java index 38c3a2e9b..ccfc26c0d 100644 --- a/convex-peer/src/main/java/convex/peer/BeliefPropagator.java +++ b/convex-peer/src/main/java/convex/peer/BeliefPropagator.java @@ -15,6 +15,7 @@ import convex.core.cpos.Belief; import convex.core.cpos.BeliefMerge; import convex.core.cpos.Block; +import convex.core.cpos.CPoSConstants; import convex.core.cpos.Order; import convex.core.crypto.AKeyPair; import convex.core.data.ACell; @@ -402,7 +403,7 @@ private Message createFullUpdateMessage() throws IOException { Message msg = createBelief(belief, novelty); long messageSize=msg.getMessageData().count(); - if (messageSize>=Format.MAX_MESSAGE_LENGTH*0.95) { + if (messageSize>=CPoSConstants.MAX_MESSAGE_LENGTH*0.95) { log.warn("Long Belief Delta message: "+messageSize); } return msg; @@ -434,7 +435,7 @@ private Message createQuickUpdateMessage() throws IOException { Message msg = createBelief(order, novelty); long messageSize=msg.getMessageData().count(); - if (messageSize>=Format.MAX_MESSAGE_LENGTH*0.95) { + if (messageSize>=CPoSConstants.MAX_MESSAGE_LENGTH*0.95) { log.warn("Long Belief Delta message: "+messageSize); } return msg;