Skip to content

Add testmode #426

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 31 commits into from
Apr 24, 2018
Merged

Add testmode #426

merged 31 commits into from
Apr 24, 2018

Conversation

LukasGasior1
Copy link
Contributor

@LukasGasior1 LukasGasior1 commented Mar 19, 2018

  • added JSON RPC via Unix Domain Socket
  • added testmode
  • 1831 out of 1837 Solidity test cases passing (6 failing)

TODO:

  • unit tests (at least for IPC, test_ endpoints are kind of covered by those Solidity tests anyway)
  • fix remaining 6 Solidity tests (possibly another PR) // edit: it seems, at lest some of these tests, are failing because of Mantis not properly handling "pending" block parameter in RPC api, see task: EC-510 for more details

gas <- optionalQuantity(obj \ "gas")
gasPrice <- optionalQuantity(obj \ "gasPrice")
value <- optionalQuantity(obj \ "value")
functionName = (obj \ "functionName").extractOpt[String]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we've previously used the name "function" for this field instead of "functionName". If you don't have a strong opinion, it may be simpler to do the same thing here.

for {
eIP150ForkBlock <- extractQuantity(blockchainParamsJson \ "EIP150ForkBlock")
eIP158ForkBlock <- extractQuantity(blockchainParamsJson \ "EIP150ForkBlock")
accountStartNonce <- extractQuantity(blockchainParamsJson \ "EIP150ForkBlock")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code looks a little suspicious to me. Is this a copy/paste error or was it deliberate?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol! I totally overlooked that.

}

network.rpc {
apis = "eth,web3,net,personal,daedalus,test"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should include iele if we want to use testmode for the solidity-to-iele test suite, right?

}
}

def ieleCall(req: IeleCallRequest): ServiceResponse[CallResponse] = {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iele transactions return a list of values rather than a single value, so I believe you want to make a IeleCallResponse class and perform rlp decoding in this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

messageHandler: MessageHandler,
testMode: Boolean)
extends Logger {

def sendHello(version: String, blockchainConfig: BlockchainConfig): Unit = {
val config = BlockchainConfigForEvm(blockchainConfig)
val configMsg = msg.Hello.Config.EthereumConfig(buildEthereumConfigMsg(BlockchainConfigForEvm(blockchainConfig)))
val configMsg = externalVmConfig.vmType match {
case VmConfig.ExternalConfig.VmTypeIele => msg.Hello.Config.IeleConfig(buildIeleConfigMsg())

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

based on our discussion on slack, weren't we planning for our near-term solution to be to make IeleConfig also be passed on every CallContext?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rtkaczyk
Copy link
Contributor

Could you add some info on how to setup and run solidity tests?

@LukasGasior1
Copy link
Contributor Author

@rtkaczyk

by the way, DO NOT REVIEW 😄 I'm resolving conflicts and doing a small refactor on this occasion

@@ -20,8 +20,19 @@ case class TransactionRequest(
nonce = nonce.getOrElse(defaultNonce),
gasPrice = gasPrice.getOrElse(defaultGasPrice),
gasLimit = gasLimit.getOrElse(defaultGasLimit),
receivingAddress = to,
receivingAddress = if (to.contains(Address(0))) None else to,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this is the default behavior in cpp-ethereum (and what solidity tests expect). To me it (kind of) makes sense, but is a bit counterintuitive for caller (what if you really want to send tx to address 0x0?). WDYT? Other solution is to use this logic only in testmode.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, you know... https://gastracker.io/addr/0x0000000000000000000000000000000000000000

This is of course a result of many mistakes, but maybe, for reasons beyond my imagination, someone has interest in sending Ether there. I'd say if it's not too much trouble go with testmode only, if not, don't worry about it.

Btw, the expression could be simplified as:

to.filter(_ != Address(0))

# See: https://github.com/input-output-hk/mantis/wiki/Creating-self-signed-certificate-for-using-JSON-RPC-with-HTTPS
mode = "http"

# Whether to enable JSON-RPC endpoint
Copy link
Contributor

@loverdos loverdos Mar 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be

Whether to enable JSON-RPC HTTP(S) endpoint

?

Also, minor, we could use the shorter HTTP(S) instead of HTTP/HTTPS

@loverdos
Copy link
Contributor

There is a compilation error:

[error] /mantis/src/snappy/scala/io/iohk/ethereum/snappy/Prerequisites.scala:53: not found: type ValidatorsBuilder
[error]   private val components = new StdTestConsensusBuilder with ValidatorsBuilder with SyncConfigBuilder {
[error]                                                             ^
[error] one error found

@@ -353,12 +382,6 @@ trait OmmersPoolBuilder {
lazy val ommersPool: ActorRef = system.actorOf(OmmersPool.props(blockchain, ommersPoolSize))
}

trait ValidatorsBuilder {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This deletion causes a compilation error in snappy. Check io.iohk.ethereum.snappy.Prerequisites.components.

@@ -17,6 +17,9 @@ mantis {
# timeout for shutting down the ActorSystem
shutdown-timeout = "15.seconds"

# Whether to run Mantis in test mode (enables test_ RPC endpoints; similar to --test flag in cpp-ethereum)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it change other functionality? If not, could be moved into rpc section.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does. If running in testmode we use different Ledger/Consensus implementations.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case maybe we should document how the functionality differs in test mode? Or is that already documented somewhere else?

override def run(): Unit = {
while (!serverSocket.isClosed) {
val clientSocket = serverSocket.accept()
new ClientThread(jsonRpcController, clientSocket).start()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we just piling up these threads?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's one thread per connection; it stops when connection closes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use a thread pool?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@loverdos after taking a second thought I'm not sure if it's the best idea. What are the benefits of using a thread pool here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid a new thread for each new connection?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean sharing one thread for multiple connections? I think it doesn't make sense with current approach (we're using blocking socket reading)

Copy link
Contributor

@loverdos loverdos Apr 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am mostly concerned with preventing an unbounded number of threads and/or outstanding requests and I was thinking in the lines of using an appropriately configured Executor.

blockchain: BlockchainImpl,
syncConfig: SyncConfig,
consensus: Consensus,
var blockchainConfig: BlockchainConfig) { // var as it's modifiable by test_ RPC endpoints
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we just create a new instance of this class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really. It's a bit tricky, because Ledger is being passed as a parameter to various other components on initialization (see NodeBuilder.scala). I wanted to create new instances, but I didn't find an elegant way to update those classes with new ledger (without introducing a var elsewhere).

blockchain: BlockchainImpl,
blockchainConfig: BlockchainConfig,
consensusConfig: ConsensusConfig,
var blockTimestamp: Long = 0) // var, because it can be modified by test_ RPC endpoints
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar as above, couldn't we create a new instance?

@rtkaczyk
Copy link
Contributor

rtkaczyk commented Apr 9, 2018

unit tests (at least for IPC, test_ endpoints are kind of covered by those Solidity tests anyway)

I take it this was done?

@rtkaczyk
Copy link
Contributor

@LukasGasior1 I'm unable to run the tests. Trying the command you provided:

$ ETH_TEST_IPC="~/.mantis-testmode/mantis.ipc" ./test/soltest -- --no-smt
bash: ./test/soltest: No such file or directory

So I tried:

$ find . -name soltest
./build/test/soltest

$ ETH_TEST_IPC="~/.mantis-testmode/mantis.ipc" ./build/test/soltest -- --no-smt
Test setup error: std::exception: No test path specified. The --testpath argument is required.

Any hints?


def close(): Unit = {
serverSocket.close()
removeSocketFile()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If close() throws an Exception, the file will not be removed.

@loverdos
Copy link
Contributor

@LukasGasior1 Could you git cherry-pick 0b033e2 ? It will make testing a bit easier.

Christos KK Loverdos and others added 2 commits April 11, 2018 17:28
Also drop the errors in favour of warnings for just a bit better
developer experience.
@rtkaczyk
Copy link
Contributor

$ ETH_TEST_IPC="$HOME/.mantis-testmode/mantis.ipc" ./build/test/soltest -- --no-smt
Running 1837 test cases...
/home/radek/iele/solidity/test/libsolidity/SolidityEndToEndTest.cpp(1502): error in "blockchain": Invalid encoded data
   Result                                                           Expectation
   ..............................................................1c ..............................................................1c
   ........................1212121212121212121212121212121212121212 ........................1212121212121212121212121212121212121212
 X ...............................................................6 ...............................................................7

/home/radek/iele/solidity/test/libsolidity/SolidityEndToEndTest.cpp(1552): error in "now": Invalid encoded data
   Result                                                           Expectation
   ...............................................................1 ...............................................................1
 X .............................................................22c .............................................................22d

/home/radek/iele/solidity/test/libsolidity/SolidityEndToEndTest.cpp(10784): error in "snark": check callContractFunction("pair()") == encodeArgs(true) failed
/home/radek/iele/solidity/test/libsolidity/SolidityEndToEndTest.cpp(10785): error in "snark": check callContractFunction("verifyTx()") == encodeArgs(true) failed
/home/radek/iele/solidity/test/contracts/Wallet.cpp(574): error in "multisig_value_transfer": check balanceAt(destination) == 100 failed [0 != 100]
/home/radek/iele/solidity/test/contracts/Wallet.cpp(644): error in "revoke_transaction": check balanceAt(destination) == 100 failed [0 != 100]

*** 6 failures detected in test suite "SolidityTests"

Looking good, but do we have a plan to fix the remaining tests?

@LukasGasior1
Copy link
Contributor Author

@rtkaczyk I've spent some time trying to figure them out. I think they may be related to the bug with "pending" as block param. To be honest, I wouldn't like to spend more time investigating it. Especially that this task was originally a minor thing.

Łukasz Gąsior added 2 commits April 16, 2018 14:08
@loverdos
Copy link
Contributor

@LukasGasior1
So, trying to understand what is going on: We introduce a new service, TestService, which we interface to the outer world via the existing JsonRpcController. The TestService initially starts with the original Mantis configuration but it will deviate as soon as some calls are made to:

  private def handleTestRequest(testService: TestService): PartialFunction[JsonRpcRequest, Future[JsonRpcResponse]] = ...

The crucial observation is that TestService and the related testmode facilities live in parallel to the rest of Mantis. If this is correct, then we do not have to worry about introducing inconsistencies to the rest of Mantis.

Having said that, using vars probably exposes the state a bit too eagerly. We could model state changes by respective updateXXX methods, e.g. updateBlockChainConfig. This resembles firing an event and gives the receiver the advantage of internally doing whatever else is necessary. This way we can fight possible inconsistencies inside the whole testmode.

So, for example, when we update blockchainConfig

var blockchainConfig: BlockchainConfig) { // var as it's modifiable by test_ RPC endpoints
in TestService
testLedgerWrapper.blockchainConfig = newBlockchainConfig
what does it mean to just set a new value? Does it have to propagate further?

I am not saying there are inconsistencies with the current state change approach but I can identify a potential source.

@LukasGasior1
Copy link
Contributor Author

LukasGasior1 commented Apr 18, 2018

@loverdos

The main problem here is that, when we update the blockchain config in testmode, it should also be updated globally (wherever Ledger or related classes are used - miner etc). I solved that by introducing TestLedgerWrapper (notice the ledger implementation there is a def) and TestLedgerBuilder.TestLedgerProxy which delegates to the ledger in this mutable wrapper. What I wanted to avoid here is to couple non-test logic (sync, block execution etc) with testmode functionality (note, that with this solution all remaining classes don't even know they're using test/mutable ledger).

what does it mean to just set a new value? Does it have to propagate further?

yes, subsequent references to ledger (from any place) will use this new confg - thanks do this wrapper mechanism.
This approach may not be perfect, but it in my opinion it has two strong pros:

  • existing code stays untouched and is not aware of testmode existence
  • this mechanism is only used in testmode, it does not affect a "normally" running node in any way, see:
class StdNode extends BaseNode with StdLedgerBuilder with StdConsensusBuilder
class TestNode extends BaseNode with TestLedgerBuilder with TestmodeConsensusBuilder with TestServiceBuilder

so without testmode enabled, there are really no vars involved

Copy link
Contributor

@loverdos loverdos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a // Note in JsonRpcIpcServer where we create Threads, so that we can revisit this.

}

def mineBlocks(request: MineBlocksRequest): ServiceResponse[MineBlocksResponse] = {
def mineBlock(): Future[Unit] = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If consensus is not ethash do we still want to run mineBlock?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ OK I saw that there is a specific Consensus wired, not the one used by the rest of Mantis.

@rtkaczyk
Copy link
Contributor

@LukasGasior1 can this be merged now?

@LukasGasior1 LukasGasior1 merged commit 8f4961a into phase/iele_testnet Apr 24, 2018
@LukasGasior1 LukasGasior1 deleted the feature/testmode branch April 24, 2018 10:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants