ENT-2168: Add a shell command to check for an existing transaction (#3762)

* ENT-2168: Add a shell command to check for an existing transaction

When a double-spend occurs the notary returns the hash of the consuming
transaction id. I've added a 'hash-lookup' shell command that matches
any recorded transactions on the node against this id hash to determine
whether the state has been consumed by this node (that could happen in certain race conditions).
This commit is contained in:
Andrius Dagys
2018-08-09 18:33:51 +01:00
committed by GitHub
parent 66739c138c
commit ce5f38104b
4 changed files with 159 additions and 3 deletions

View File

@ -0,0 +1,96 @@
package net.corda.tools.shell
import co.paralleluniverse.fibers.Suspendable
import com.google.common.io.Files
import com.jcraft.jsch.ChannelExec
import com.jcraft.jsch.JSch
import com.jcraft.jsch.Session
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.node.services.Permissions
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver
import net.corda.testing.node.User
import org.bouncycastle.util.io.Streams
import org.junit.Ignore
import org.junit.Test
import kotlin.test.assertTrue
class HashLookupCommandTest {
@Ignore
@Test
fun `hash lookup command returns correct response`() {
val user = User("u", "p", setOf(Permissions.all()))
driver(DriverParameters(notarySpecs = emptyList(), extraCordappPackagesToScan = listOf("net.corda.testing.contracts"))) {
val nodeFuture = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user), startInSameProcess = true)
val node = nodeFuture.getOrThrow()
val txId = issueTransaction(node)
val session = connectToShell(user, node)
testCommand(session, command = "hashLookup ${txId.sha256()}", expected = "Found a matching transaction with Id: $txId")
testCommand(session, command = "hashLookup ${SecureHash.randomSHA256()}", expected = "No matching transaction found")
session.disconnect()
}
}
private fun testCommand(session: Session, command: String, expected: String) {
val channel = session.openChannel("exec") as ChannelExec
channel.setCommand(command)
channel.connect(5000)
assertTrue(channel.isConnected)
val response = String(Streams.readAll(channel.inputStream))
val matchFound = response.lines().any { line ->
line.contains(expected)
}
channel.disconnect()
assertTrue(matchFound)
}
private fun connectToShell(user: User, node: NodeHandle): Session {
val conf = ShellConfiguration(commandsDirectory = Files.createTempDir().toPath(),
user = user.username, password = user.password,
hostAndPort = node.rpcAddress,
sshdPort = 2224)
InteractiveShell.startShell(conf)
InteractiveShell.nodeInfo()
val session = JSch().getSession("u", "localhost", 2224)
session.setConfig("StrictHostKeyChecking", "no")
session.setPassword("p")
session.connect()
assertTrue(session.isConnected)
return session
}
private fun issueTransaction(node: NodeHandle): SecureHash {
return node.rpc.startFlow(::DummyIssue).returnValue.get()
}
@StartableByRPC
internal class DummyIssue : FlowLogic<SecureHash>() {
@Suspendable
override fun call(): SecureHash {
val me = serviceHub.myInfo.legalIdentities.first().ref(0)
val fakeNotary = me.party
val builder = DummyContract.generateInitial(1, fakeNotary as Party, me)
val stx = serviceHub.signInitialTransaction(builder)
serviceHub.recordTransactions(stx)
return stx.id
}
}
}