Add CALLSTATELESS - POST queue and no fee for zero size memory increase
This commit is contained in:
parent
70479bdd02
commit
57f9db3969
|
@ -17,10 +17,8 @@ public class MessageCall {
|
|||
|
||||
/** gas to pay for the call, remaining gas will be refunded to the caller */
|
||||
private DataWord gas;
|
||||
/** address to call */
|
||||
private DataWord toAddress;
|
||||
/** address of the storage to use */
|
||||
private DataWord storageAddress;
|
||||
/** address of account which code to call */
|
||||
private DataWord codeAddress;
|
||||
/** the value that can be transfer along with the code execution */
|
||||
private DataWord endowment;
|
||||
/** 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 */
|
||||
private DataWord outDataSize;
|
||||
|
||||
public MessageCall(MsgType type, DataWord gas, DataWord toAddress,
|
||||
DataWord storageAddress, DataWord endowment,
|
||||
DataWord inDataOffs, DataWord inDataSize) {
|
||||
public MessageCall(MsgType type, DataWord gas, DataWord codeAddress,
|
||||
DataWord endowment, DataWord inDataOffs, DataWord inDataSize) {
|
||||
this.type = type;
|
||||
this.gas = gas;
|
||||
this.toAddress = toAddress;
|
||||
this.storageAddress = (type == MsgType.STATELESS) ? storageAddress: toAddress;
|
||||
this.codeAddress = codeAddress;
|
||||
this.endowment = endowment;
|
||||
this.inDataOffs = inDataOffs;
|
||||
this.inDataSize = inDataSize;
|
||||
}
|
||||
|
||||
public MessageCall(MsgType type, DataWord gas, DataWord toAddress,
|
||||
DataWord storageAddress, DataWord endowment,
|
||||
DataWord inDataOffs, DataWord inDataSize, DataWord outDataOffs,
|
||||
DataWord outDataSize) {
|
||||
this(type, gas, toAddress, storageAddress, endowment, inDataOffs, inDataSize);
|
||||
public MessageCall(MsgType type, DataWord gas, DataWord codeAddress,
|
||||
DataWord endowment, DataWord inDataOffs, DataWord inDataSize,
|
||||
DataWord outDataOffs, DataWord outDataSize) {
|
||||
this(type, gas, codeAddress, endowment, inDataOffs, inDataSize);
|
||||
this.outDataOffs = outDataOffs;
|
||||
this.outDataSize = outDataSize;
|
||||
}
|
||||
|
@ -61,12 +56,8 @@ public class MessageCall {
|
|||
return gas;
|
||||
}
|
||||
|
||||
public DataWord getToAddress() {
|
||||
return toAddress;
|
||||
}
|
||||
|
||||
public DataWord getStorageAddress() {
|
||||
return storageAddress;
|
||||
public DataWord getCodeAddress() {
|
||||
return codeAddress;
|
||||
}
|
||||
|
||||
public DataWord getEndowment() {
|
||||
|
|
|
@ -5,6 +5,7 @@ import org.ethereum.db.ContractDetails;
|
|||
import org.ethereum.facade.Repository;
|
||||
import org.ethereum.util.ByteUtil;
|
||||
import org.ethereum.util.Utils;
|
||||
import org.ethereum.vm.MessageCall.MsgType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
@ -17,6 +18,7 @@ import java.util.Collections;
|
|||
import java.util.EmptyStackException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
|
@ -31,7 +33,13 @@ public class Program {
|
|||
private int invokeHash;
|
||||
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<>();
|
||||
ByteBuffer memory = null;
|
||||
|
@ -239,8 +247,9 @@ public class Program {
|
|||
*/
|
||||
protected void allocateMemory(int offset, int size) {
|
||||
|
||||
int memSize = memory != null ? memory.limit(): 0;
|
||||
double newMemSize = Math.max(memSize, Math.ceil((double)(offset + size) / 32) * 32);
|
||||
int memSize = memory != null ? memory.limit() : 0;
|
||||
double newMemSize = Math.max(memSize, size != 0 ?
|
||||
Math.ceil((double) (offset + size) / 32) * 32 : 0);
|
||||
ByteBuffer tmpMem = ByteBuffer.allocate((int)newMemSize);
|
||||
if (memory != null)
|
||||
tmpMem.put(memory.array(), 0, memory.limit());
|
||||
|
@ -344,47 +353,40 @@ public class Program {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instead of immediately executing a 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.
|
||||
*
|
||||
* @param msg is the message call object
|
||||
*/
|
||||
public void queue(MessageCall msg) {
|
||||
postQueue.push(msg);
|
||||
public Queue<MessageCall> getMessageQueue() {
|
||||
return messageQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public void callToAddress(MessageCall msg) {
|
||||
|
||||
// TODO: update code for CALLSTATELESS
|
||||
|
||||
ByteBuffer data = memoryChunk(msg.getInDataOffs(), msg.getInDataSize());
|
||||
|
||||
// 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
|
||||
byte[] programCode = this.result.getRepository().getCode(toAddress);
|
||||
byte[] programCode = this.result.getRepository().getCode(codeAddress);
|
||||
|
||||
if (logger.isInfoEnabled())
|
||||
logger.info("calling for existing contract: address: [ {} ], outDataOffs: [ {} ], outDataSize: [ {} ] ",
|
||||
Hex.toHexString(toAddress), msg.getOutDataOffs().longValue(), msg.getOutDataSize().longValue());
|
||||
|
||||
byte[] senderAddress = this.getOwnerAddress().getLast20Bytes();
|
||||
logger.info(msg.getType().name() + " for existing contract: address: [ {} ], outDataOffs: [ {} ], outDataSize: [ {} ] ",
|
||||
Hex.toHexString(contextAddress), msg.getOutDataOffs().longValue(), msg.getOutDataSize().longValue());
|
||||
|
||||
// 2.1 PERFORM THE GAS VALUE TX
|
||||
// (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION)
|
||||
if (this.getGas().longValue() - msg.getGas().longValue() < 0 ) {
|
||||
logger.info("No gas for the internal call, \n" +
|
||||
"fromAddress={}, toAddress={}",
|
||||
Hex.toHexString(senderAddress), Hex.toHexString(toAddress));
|
||||
"fromAddress={}, codeAddress={}",
|
||||
Hex.toHexString(senderAddress), Hex.toHexString(contextAddress));
|
||||
this.stackPushZero();
|
||||
return;
|
||||
}
|
||||
|
@ -403,7 +405,7 @@ public class Program {
|
|||
stackPushOne();
|
||||
|
||||
this.getResult().addCallCreate(data.array(),
|
||||
msg.getToAddress().getLast20Bytes(),
|
||||
msg.getCodeAddress().getLast20Bytes(),
|
||||
msg.getGas().getNoLeadZeroesData(),
|
||||
msg.getEndowment().getNoLeadZeroesData());
|
||||
|
||||
|
@ -415,11 +417,11 @@ public class Program {
|
|||
|
||||
Repository trackRepository = result.getRepository().getTrack();
|
||||
trackRepository.startTracking();
|
||||
trackRepository.addBalance(toAddress, msg.getEndowment().value());
|
||||
trackRepository.addBalance(contextAddress, msg.getEndowment().value());
|
||||
|
||||
ProgramInvoke programInvoke =
|
||||
ProgramInvokeFactory.createProgramInvoke(this, msg.getToAddress(),
|
||||
msg.getEndowment(), msg.getGas(), result.getRepository().getBalance(toAddress),
|
||||
ProgramInvokeFactory.createProgramInvoke(this, msg.getCodeAddress(),
|
||||
msg.getEndowment(), msg.getGas(), result.getRepository().getBalance(contextAddress),
|
||||
data.array(),
|
||||
trackRepository, this.invokeData.getCallDeep() + 1);
|
||||
|
||||
|
@ -436,7 +438,7 @@ public class Program {
|
|||
if (result != null &&
|
||||
result.getException() != null &&
|
||||
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();
|
||||
stackPushZero();
|
||||
|
|
|
@ -98,14 +98,13 @@ public class VM {
|
|||
break;
|
||||
case SSTORE:
|
||||
program.stackRequire(2);
|
||||
// for gas calculations [YP 9.2]
|
||||
DataWord newValue = stack.get(stack.size()-2);
|
||||
DataWord oldValue = program.storageLoad(stack.peek());
|
||||
if (oldValue == null && !newValue.isZero()) {
|
||||
if (oldValue == null && !newValue.isZero())
|
||||
gasCost = GasCost.SSTORE * 2;
|
||||
} else if (oldValue != null && newValue.isZero()) {
|
||||
else if (oldValue != null && newValue.isZero())
|
||||
gasCost = GasCost.SSTORE * 0;
|
||||
} else
|
||||
else
|
||||
gasCost = GasCost.SSTORE;
|
||||
break;
|
||||
case SLOAD:
|
||||
|
@ -118,36 +117,36 @@ public class VM {
|
|||
// These all operate on memory and therefore potentially expand it:
|
||||
case MSTORE:
|
||||
program.stackRequire(2);
|
||||
newMemSize = stack.peek().value().add(BigInteger.valueOf(32));
|
||||
newMemSize = memNeeded(stack.peek(), new DataWord(32));
|
||||
break;
|
||||
case MSTORE8:
|
||||
program.stackRequire(2);
|
||||
newMemSize = stack.peek().value().add(BigInteger.ONE);
|
||||
newMemSize = memNeeded(stack.peek(), new DataWord(1));
|
||||
break;
|
||||
case MLOAD:
|
||||
program.stackRequire(1);
|
||||
newMemSize = stack.peek().value().add(BigInteger.valueOf(32));
|
||||
newMemSize = memNeeded(stack.peek(), new DataWord(32));
|
||||
break;
|
||||
case RETURN:
|
||||
program.stackRequire(2);
|
||||
newMemSize = stack.peek().value().add(stack.get(stack.size()-2).value());
|
||||
newMemSize = memNeeded(stack.peek(), stack.get(stack.size()-2));
|
||||
break;
|
||||
case SHA3:
|
||||
program.stackRequire(2);
|
||||
gasCost = GasCost.SHA3;
|
||||
newMemSize = stack.peek().value().add(stack.get(stack.size()-2).value());
|
||||
newMemSize = memNeeded(stack.peek(), stack.get(stack.size()-2));
|
||||
break;
|
||||
case CALLDATACOPY:
|
||||
program.stackRequire(3);
|
||||
newMemSize = stack.peek().value().add(stack.get(stack.size()-3).value());
|
||||
newMemSize = memNeeded(stack.peek(), stack.get(stack.size()-3));
|
||||
break;
|
||||
case CODECOPY:
|
||||
program.stackRequire(3);
|
||||
newMemSize = stack.peek().value().add(stack.get(stack.size()-3).value());
|
||||
newMemSize = memNeeded(stack.peek(), stack.get(stack.size()-3));
|
||||
break;
|
||||
case EXTCODECOPY:
|
||||
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;
|
||||
case CALL: case CALLSTATELESS:
|
||||
program.stackRequire(7);
|
||||
|
@ -157,9 +156,9 @@ public class VM {
|
|||
throw program.new OutOfGasException();
|
||||
}
|
||||
callGas = callGasWord.longValue();
|
||||
BigInteger x = stack.get(stack.size()-4).value().add(stack.get(stack.size()-5).value()); // in offset+size
|
||||
BigInteger y = stack.get(stack.size()-6).value().add(stack.get(stack.size()-7).value()); // out offset+size
|
||||
newMemSize = x.max(y);
|
||||
BigInteger in = memNeeded(stack.get(stack.size()-4), stack.get(stack.size()-5)); // in offset+size
|
||||
BigInteger out = memNeeded(stack.get(stack.size()-6), stack.get(stack.size()-7)); // out offset+size
|
||||
newMemSize = in.max(out);
|
||||
break;
|
||||
case POST:
|
||||
program.stackRequire(5);
|
||||
|
@ -168,7 +167,7 @@ public class VM {
|
|||
case CREATE:
|
||||
program.stackRequire(3);
|
||||
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;
|
||||
default:
|
||||
break;
|
||||
|
@ -877,7 +876,7 @@ public class VM {
|
|||
} break;
|
||||
case CALL: case CALLSTATELESS: {
|
||||
DataWord gas = program.stackPop();
|
||||
DataWord toAddress = program.stackPop();
|
||||
DataWord codeAddress = program.stackPop();
|
||||
DataWord value = program.stackPop();
|
||||
|
||||
DataWord inDataOffs = program.stackPop();
|
||||
|
@ -887,7 +886,7 @@ public class VM {
|
|||
DataWord outDataSize = program.stackPop();
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
hint = "addr: " + Hex.toHexString(toAddress.getLast20Bytes())
|
||||
hint = "addr: " + Hex.toHexString(codeAddress.getLast20Bytes())
|
||||
+ " gas: " + gas.shortHex()
|
||||
+ " inOff: " + inDataOffs.shortHex()
|
||||
+ " inSize: " + inDataSize.shortHex();
|
||||
|
@ -899,8 +898,8 @@ public class VM {
|
|||
|
||||
MessageCall msg = new MessageCall(
|
||||
op.equals(CALL) ? MsgType.CALL : MsgType.STATELESS,
|
||||
gas, toAddress, program.getOwnerAddress(), value,
|
||||
inDataOffs, inDataSize, outDataOffs, outDataSize);
|
||||
gas, codeAddress, value, inDataOffs, inDataSize,
|
||||
outDataOffs, outDataSize);
|
||||
program.callToAddress(msg);
|
||||
|
||||
program.step();
|
||||
|
@ -908,14 +907,14 @@ public class VM {
|
|||
case POST:{
|
||||
program.stackRequire(5);
|
||||
DataWord gas = program.stackPop();
|
||||
DataWord toAddress = program.stackPop();
|
||||
DataWord codeAddress= program.stackPop();
|
||||
DataWord value = program.stackPop();
|
||||
|
||||
DataWord inDataOffs = program.stackPop();
|
||||
DataWord inDataSize = program.stackPop();
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
hint = "addr: " + Hex.toHexString(toAddress.getLast20Bytes())
|
||||
hint = "addr: " + Hex.toHexString(codeAddress.getLast20Bytes())
|
||||
+ " gas: " + gas.shortHex()
|
||||
+ " inOff: " + inDataOffs.shortHex()
|
||||
+ " inSize: " + inDataSize.shortHex();
|
||||
|
@ -925,9 +924,9 @@ public class VM {
|
|||
program.invokeData.getCallDeep(), hint);
|
||||
}
|
||||
|
||||
MessageCall msgCall = new MessageCall(MsgType.POST, gas, toAddress,
|
||||
program.getOwnerAddress(), value, inDataOffs, inDataSize);
|
||||
program.queue(msgCall);
|
||||
MessageCall msgCall = new MessageCall(MsgType.POST, gas,
|
||||
codeAddress, value, inDataOffs, inDataSize);
|
||||
program.getMessageQueue().add(msgCall);
|
||||
|
||||
program.step();
|
||||
} 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
|
||||
* - standard Not Yet Implemented
|
||||
|
|
|
@ -148,7 +148,7 @@ public class ProgramMemoryTest {
|
|||
int size = 0;
|
||||
program.memory = memory;
|
||||
program.allocateMemory(offset, size);
|
||||
assertEquals(96, program.getMemSize());
|
||||
assertEquals(64, program.getMemSize());
|
||||
}
|
||||
|
||||
/************************************************/
|
||||
|
@ -162,7 +162,7 @@ public class ProgramMemoryTest {
|
|||
int size = 0;
|
||||
program.memory = memory;
|
||||
program.allocateMemory(offset, size);
|
||||
assertEquals(32, program.getMemSize());
|
||||
assertEquals(0, program.getMemSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in New Issue