Add CALLSTATELESS - POST queue and no fee for zero size memory increase

This commit is contained in:
nicksavers 2014-09-17 14:27:45 +02:00
parent 70479bdd02
commit 57f9db3969
4 changed files with 82 additions and 76 deletions

View File

@ -17,10 +17,8 @@ public class MessageCall {
/** gas to pay for the call, remaining gas will be refunded to the caller */ /** gas to pay for the call, remaining gas will be refunded to the caller */
private DataWord gas; private DataWord gas;
/** address to call */ /** address of account which code to call */
private DataWord toAddress; private DataWord codeAddress;
/** address of the storage to use */
private DataWord storageAddress;
/** the value that can be transfer along with the code execution */ /** the value that can be transfer along with the code execution */
private DataWord endowment; private DataWord endowment;
/** start of memory to be input data to the call */ /** start of memory to be input data to the call */
@ -32,23 +30,20 @@ public class MessageCall {
/** size of memory to be output data to the call */ /** size of memory to be output data to the call */
private DataWord outDataSize; private DataWord outDataSize;
public MessageCall(MsgType type, DataWord gas, DataWord toAddress, public MessageCall(MsgType type, DataWord gas, DataWord codeAddress,
DataWord storageAddress, DataWord endowment, DataWord endowment, DataWord inDataOffs, DataWord inDataSize) {
DataWord inDataOffs, DataWord inDataSize) {
this.type = type; this.type = type;
this.gas = gas; this.gas = gas;
this.toAddress = toAddress; this.codeAddress = codeAddress;
this.storageAddress = (type == MsgType.STATELESS) ? storageAddress: toAddress;
this.endowment = endowment; this.endowment = endowment;
this.inDataOffs = inDataOffs; this.inDataOffs = inDataOffs;
this.inDataSize = inDataSize; this.inDataSize = inDataSize;
} }
public MessageCall(MsgType type, DataWord gas, DataWord toAddress, public MessageCall(MsgType type, DataWord gas, DataWord codeAddress,
DataWord storageAddress, DataWord endowment, DataWord endowment, DataWord inDataOffs, DataWord inDataSize,
DataWord inDataOffs, DataWord inDataSize, DataWord outDataOffs, DataWord outDataOffs, DataWord outDataSize) {
DataWord outDataSize) { this(type, gas, codeAddress, endowment, inDataOffs, inDataSize);
this(type, gas, toAddress, storageAddress, endowment, inDataOffs, inDataSize);
this.outDataOffs = outDataOffs; this.outDataOffs = outDataOffs;
this.outDataSize = outDataSize; this.outDataSize = outDataSize;
} }
@ -61,12 +56,8 @@ public class MessageCall {
return gas; return gas;
} }
public DataWord getToAddress() { public DataWord getCodeAddress() {
return toAddress; return codeAddress;
}
public DataWord getStorageAddress() {
return storageAddress;
} }
public DataWord getEndowment() { public DataWord getEndowment() {

View File

@ -5,6 +5,7 @@ import org.ethereum.db.ContractDetails;
import org.ethereum.facade.Repository; import org.ethereum.facade.Repository;
import org.ethereum.util.ByteUtil; import org.ethereum.util.ByteUtil;
import org.ethereum.util.Utils; import org.ethereum.util.Utils;
import org.ethereum.vm.MessageCall.MsgType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex; import org.spongycastle.util.encoders.Hex;
@ -17,6 +18,7 @@ import java.util.Collections;
import java.util.EmptyStackException; import java.util.EmptyStackException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Queue;
import java.util.Stack; import java.util.Stack;
/** /**
@ -31,7 +33,13 @@ public class Program {
private int invokeHash; private int invokeHash;
private ProgramListener listener; private ProgramListener listener;
private static LinkedList<MessageCall> postQueue = new LinkedList<>(); /**
* Instead of immediately executing a POST message call,
* it is added to a post-queue, to be executed after everything else
* (including prior-created posts) within the scope
* of that transaction execution is executed.
*/
private static Queue<MessageCall> messageQueue = new LinkedList<>();
Stack<DataWord> stack = new Stack<>(); Stack<DataWord> stack = new Stack<>();
ByteBuffer memory = null; ByteBuffer memory = null;
@ -239,8 +247,9 @@ public class Program {
*/ */
protected void allocateMemory(int offset, int size) { protected void allocateMemory(int offset, int size) {
int memSize = memory != null ? memory.limit(): 0; int memSize = memory != null ? memory.limit() : 0;
double newMemSize = Math.max(memSize, Math.ceil((double)(offset + size) / 32) * 32); double newMemSize = Math.max(memSize, size != 0 ?
Math.ceil((double) (offset + size) / 32) * 32 : 0);
ByteBuffer tmpMem = ByteBuffer.allocate((int)newMemSize); ByteBuffer tmpMem = ByteBuffer.allocate((int)newMemSize);
if (memory != null) if (memory != null)
tmpMem.put(memory.array(), 0, memory.limit()); tmpMem.put(memory.array(), 0, memory.limit());
@ -344,47 +353,40 @@ public class Program {
} }
} }
/** public Queue<MessageCall> getMessageQueue() {
* Instead of immediately executing a message call, it is return messageQueue;
* added to a post-queue, to be executed after everything else
* (including prior-created posts) within the scope
* of that transaction execution is executed.
*
* @param msg is the message call object
*/
public void queue(MessageCall msg) {
postQueue.push(msg);
} }
/** /**
* That method is for internal code invocations * That method is for internal code invocations
* *
* - Normal calls invoke a specified contract which updates itself
* - Stateless calls invoke another contract, within the context of the caller
*
* @param msg is the message call object * @param msg is the message call object
*/ */
public void callToAddress(MessageCall msg) { public void callToAddress(MessageCall msg) {
// TODO: update code for CALLSTATELESS
ByteBuffer data = memoryChunk(msg.getInDataOffs(), msg.getInDataSize()); ByteBuffer data = memoryChunk(msg.getInDataOffs(), msg.getInDataSize());
// FETCH THE SAVED STORAGE // FETCH THE SAVED STORAGE
byte[] toAddress = msg.getToAddress().getLast20Bytes(); byte[] codeAddress = msg.getCodeAddress().getLast20Bytes();
byte[] senderAddress = this.getOwnerAddress().getLast20Bytes();
byte[] contextAddress = msg.getType() == MsgType.STATELESS ? senderAddress : codeAddress;
// FETCH THE CODE // FETCH THE CODE
byte[] programCode = this.result.getRepository().getCode(toAddress); byte[] programCode = this.result.getRepository().getCode(codeAddress);
if (logger.isInfoEnabled()) if (logger.isInfoEnabled())
logger.info("calling for existing contract: address: [ {} ], outDataOffs: [ {} ], outDataSize: [ {} ] ", logger.info(msg.getType().name() + " for existing contract: address: [ {} ], outDataOffs: [ {} ], outDataSize: [ {} ] ",
Hex.toHexString(toAddress), msg.getOutDataOffs().longValue(), msg.getOutDataSize().longValue()); Hex.toHexString(contextAddress), msg.getOutDataOffs().longValue(), msg.getOutDataSize().longValue());
byte[] senderAddress = this.getOwnerAddress().getLast20Bytes();
// 2.1 PERFORM THE GAS VALUE TX // 2.1 PERFORM THE GAS VALUE TX
// (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION) // (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION)
if (this.getGas().longValue() - msg.getGas().longValue() < 0 ) { if (this.getGas().longValue() - msg.getGas().longValue() < 0 ) {
logger.info("No gas for the internal call, \n" + logger.info("No gas for the internal call, \n" +
"fromAddress={}, toAddress={}", "fromAddress={}, codeAddress={}",
Hex.toHexString(senderAddress), Hex.toHexString(toAddress)); Hex.toHexString(senderAddress), Hex.toHexString(contextAddress));
this.stackPushZero(); this.stackPushZero();
return; return;
} }
@ -403,7 +405,7 @@ public class Program {
stackPushOne(); stackPushOne();
this.getResult().addCallCreate(data.array(), this.getResult().addCallCreate(data.array(),
msg.getToAddress().getLast20Bytes(), msg.getCodeAddress().getLast20Bytes(),
msg.getGas().getNoLeadZeroesData(), msg.getGas().getNoLeadZeroesData(),
msg.getEndowment().getNoLeadZeroesData()); msg.getEndowment().getNoLeadZeroesData());
@ -415,11 +417,11 @@ public class Program {
Repository trackRepository = result.getRepository().getTrack(); Repository trackRepository = result.getRepository().getTrack();
trackRepository.startTracking(); trackRepository.startTracking();
trackRepository.addBalance(toAddress, msg.getEndowment().value()); trackRepository.addBalance(contextAddress, msg.getEndowment().value());
ProgramInvoke programInvoke = ProgramInvoke programInvoke =
ProgramInvokeFactory.createProgramInvoke(this, msg.getToAddress(), ProgramInvokeFactory.createProgramInvoke(this, msg.getCodeAddress(),
msg.getEndowment(), msg.getGas(), result.getRepository().getBalance(toAddress), msg.getEndowment(), msg.getGas(), result.getRepository().getBalance(contextAddress),
data.array(), data.array(),
trackRepository, this.invokeData.getCallDeep() + 1); trackRepository, this.invokeData.getCallDeep() + 1);
@ -436,7 +438,7 @@ public class Program {
if (result != null && if (result != null &&
result.getException() != null && result.getException() != null &&
result.getException() instanceof Program.OutOfGasException) { result.getException() instanceof Program.OutOfGasException) {
logger.info("contract run halted by OutOfGas: contract={}" , Hex.toHexString(toAddress)); logger.info("contract run halted by OutOfGas: contract={}" , Hex.toHexString(contextAddress));
trackRepository.rollback(); trackRepository.rollback();
stackPushZero(); stackPushZero();

View File

@ -98,14 +98,13 @@ public class VM {
break; break;
case SSTORE: case SSTORE:
program.stackRequire(2); program.stackRequire(2);
// for gas calculations [YP 9.2]
DataWord newValue = stack.get(stack.size()-2); DataWord newValue = stack.get(stack.size()-2);
DataWord oldValue = program.storageLoad(stack.peek()); DataWord oldValue = program.storageLoad(stack.peek());
if (oldValue == null && !newValue.isZero()) { if (oldValue == null && !newValue.isZero())
gasCost = GasCost.SSTORE * 2; gasCost = GasCost.SSTORE * 2;
} else if (oldValue != null && newValue.isZero()) { else if (oldValue != null && newValue.isZero())
gasCost = GasCost.SSTORE * 0; gasCost = GasCost.SSTORE * 0;
} else else
gasCost = GasCost.SSTORE; gasCost = GasCost.SSTORE;
break; break;
case SLOAD: case SLOAD:
@ -118,36 +117,36 @@ public class VM {
// These all operate on memory and therefore potentially expand it: // These all operate on memory and therefore potentially expand it:
case MSTORE: case MSTORE:
program.stackRequire(2); program.stackRequire(2);
newMemSize = stack.peek().value().add(BigInteger.valueOf(32)); newMemSize = memNeeded(stack.peek(), new DataWord(32));
break; break;
case MSTORE8: case MSTORE8:
program.stackRequire(2); program.stackRequire(2);
newMemSize = stack.peek().value().add(BigInteger.ONE); newMemSize = memNeeded(stack.peek(), new DataWord(1));
break; break;
case MLOAD: case MLOAD:
program.stackRequire(1); program.stackRequire(1);
newMemSize = stack.peek().value().add(BigInteger.valueOf(32)); newMemSize = memNeeded(stack.peek(), new DataWord(32));
break; break;
case RETURN: case RETURN:
program.stackRequire(2); program.stackRequire(2);
newMemSize = stack.peek().value().add(stack.get(stack.size()-2).value()); newMemSize = memNeeded(stack.peek(), stack.get(stack.size()-2));
break; break;
case SHA3: case SHA3:
program.stackRequire(2); program.stackRequire(2);
gasCost = GasCost.SHA3; gasCost = GasCost.SHA3;
newMemSize = stack.peek().value().add(stack.get(stack.size()-2).value()); newMemSize = memNeeded(stack.peek(), stack.get(stack.size()-2));
break; break;
case CALLDATACOPY: case CALLDATACOPY:
program.stackRequire(3); program.stackRequire(3);
newMemSize = stack.peek().value().add(stack.get(stack.size()-3).value()); newMemSize = memNeeded(stack.peek(), stack.get(stack.size()-3));
break; break;
case CODECOPY: case CODECOPY:
program.stackRequire(3); program.stackRequire(3);
newMemSize = stack.peek().value().add(stack.get(stack.size()-3).value()); newMemSize = memNeeded(stack.peek(), stack.get(stack.size()-3));
break; break;
case EXTCODECOPY: case EXTCODECOPY:
program.stackRequire(4); program.stackRequire(4);
newMemSize = stack.get(stack.size()-2).value().add(stack.get(stack.size()-4).value()); newMemSize = memNeeded(stack.get(stack.size()-2), stack.get(stack.size()-4));
break; break;
case CALL: case CALLSTATELESS: case CALL: case CALLSTATELESS:
program.stackRequire(7); program.stackRequire(7);
@ -157,9 +156,9 @@ public class VM {
throw program.new OutOfGasException(); throw program.new OutOfGasException();
} }
callGas = callGasWord.longValue(); callGas = callGasWord.longValue();
BigInteger x = stack.get(stack.size()-4).value().add(stack.get(stack.size()-5).value()); // in offset+size BigInteger in = memNeeded(stack.get(stack.size()-4), stack.get(stack.size()-5)); // in offset+size
BigInteger y = stack.get(stack.size()-6).value().add(stack.get(stack.size()-7).value()); // out offset+size BigInteger out = memNeeded(stack.get(stack.size()-6), stack.get(stack.size()-7)); // out offset+size
newMemSize = x.max(y); newMemSize = in.max(out);
break; break;
case POST: case POST:
program.stackRequire(5); program.stackRequire(5);
@ -168,7 +167,7 @@ public class VM {
case CREATE: case CREATE:
program.stackRequire(3); program.stackRequire(3);
gasCost = GasCost.CREATE; gasCost = GasCost.CREATE;
newMemSize = stack.get(stack.size()-2).value().add(stack.get(stack.size()-3).value()); newMemSize = memNeeded(stack.get(stack.size()-2), stack.get(stack.size()-3));
break; break;
default: default:
break; break;
@ -877,7 +876,7 @@ public class VM {
} break; } break;
case CALL: case CALLSTATELESS: { case CALL: case CALLSTATELESS: {
DataWord gas = program.stackPop(); DataWord gas = program.stackPop();
DataWord toAddress = program.stackPop(); DataWord codeAddress = program.stackPop();
DataWord value = program.stackPop(); DataWord value = program.stackPop();
DataWord inDataOffs = program.stackPop(); DataWord inDataOffs = program.stackPop();
@ -887,7 +886,7 @@ public class VM {
DataWord outDataSize = program.stackPop(); DataWord outDataSize = program.stackPop();
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
hint = "addr: " + Hex.toHexString(toAddress.getLast20Bytes()) hint = "addr: " + Hex.toHexString(codeAddress.getLast20Bytes())
+ " gas: " + gas.shortHex() + " gas: " + gas.shortHex()
+ " inOff: " + inDataOffs.shortHex() + " inOff: " + inDataOffs.shortHex()
+ " inSize: " + inDataSize.shortHex(); + " inSize: " + inDataSize.shortHex();
@ -899,8 +898,8 @@ public class VM {
MessageCall msg = new MessageCall( MessageCall msg = new MessageCall(
op.equals(CALL) ? MsgType.CALL : MsgType.STATELESS, op.equals(CALL) ? MsgType.CALL : MsgType.STATELESS,
gas, toAddress, program.getOwnerAddress(), value, gas, codeAddress, value, inDataOffs, inDataSize,
inDataOffs, inDataSize, outDataOffs, outDataSize); outDataOffs, outDataSize);
program.callToAddress(msg); program.callToAddress(msg);
program.step(); program.step();
@ -908,14 +907,14 @@ public class VM {
case POST:{ case POST:{
program.stackRequire(5); program.stackRequire(5);
DataWord gas = program.stackPop(); DataWord gas = program.stackPop();
DataWord toAddress = program.stackPop(); DataWord codeAddress= program.stackPop();
DataWord value = program.stackPop(); DataWord value = program.stackPop();
DataWord inDataOffs = program.stackPop(); DataWord inDataOffs = program.stackPop();
DataWord inDataSize = program.stackPop(); DataWord inDataSize = program.stackPop();
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
hint = "addr: " + Hex.toHexString(toAddress.getLast20Bytes()) hint = "addr: " + Hex.toHexString(codeAddress.getLast20Bytes())
+ " gas: " + gas.shortHex() + " gas: " + gas.shortHex()
+ " inOff: " + inDataOffs.shortHex() + " inOff: " + inDataOffs.shortHex()
+ " inSize: " + inDataSize.shortHex(); + " inSize: " + inDataSize.shortHex();
@ -925,9 +924,9 @@ public class VM {
program.invokeData.getCallDeep(), hint); program.invokeData.getCallDeep(), hint);
} }
MessageCall msgCall = new MessageCall(MsgType.POST, gas, toAddress, MessageCall msgCall = new MessageCall(MsgType.POST, gas,
program.getOwnerAddress(), value, inDataOffs, inDataSize); codeAddress, value, inDataOffs, inDataSize);
program.queue(msgCall); program.getMessageQueue().add(msgCall);
program.step(); program.step();
} break; } break;
@ -997,6 +996,20 @@ public class VM {
} }
} }
/**
* Utility to calculate new total memory size needed for an operation.
* <br/> Basically just offset + size, unless size is 0, in which case the result is also 0.
*
* @param offset starting position of the memory
* @param size number of bytes needed
* @return offset + size, unless size is 0. In that case memNeeded is also 0.
*/
private BigInteger memNeeded(DataWord offset, DataWord size) {
if (size.isZero())
return BigInteger.ZERO;
return offset.value().add(size.value());
}
/* /*
* Dumping the VM state at the current operation in various styles * Dumping the VM state at the current operation in various styles
* - standard Not Yet Implemented * - standard Not Yet Implemented

View File

@ -148,7 +148,7 @@ public class ProgramMemoryTest {
int size = 0; int size = 0;
program.memory = memory; program.memory = memory;
program.allocateMemory(offset, size); program.allocateMemory(offset, size);
assertEquals(96, program.getMemSize()); assertEquals(64, program.getMemSize());
} }
/************************************************/ /************************************************/
@ -162,7 +162,7 @@ public class ProgramMemoryTest {
int size = 0; int size = 0;
program.memory = memory; program.memory = memory;
program.allocateMemory(offset, size); program.allocateMemory(offset, size);
assertEquals(32, program.getMemSize()); assertEquals(0, program.getMemSize());
} }
@Test @Test