Raft notaries can share a single key pair for the service identity (i… (#2269)

* Raft notaries can share a single key pair for the service identity (in contrast to a shared composite public key, and individual signing key pairs). This allows adjusting the cluster size on the fly.
This commit is contained in:
Andrius Dagys
2018-01-09 08:17:59 +00:00
committed by GitHub
parent 4a995870c8
commit 3e00676851
10 changed files with 184 additions and 93 deletions

View File

@ -60,7 +60,7 @@ class BFTNotaryServiceTests {
(Paths.get("config") / "currentView").deleteIfExists() // XXX: Make config object warn if this exists?
val replicaIds = (0 until clusterSize)
notary = DevIdentityGenerator.generateDistributedNotaryIdentity(
notary = DevIdentityGenerator.generateDistributedNotaryCompositeIdentity(
replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) },
CordaX500Name("BFT", "Zurich", "CH"))

View File

@ -16,11 +16,11 @@ import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.nodeapi.internal.config.User
import net.corda.testing.*
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.PortAllocation
import net.corda.testing.driver.driver
import net.corda.testing.node.ClusterSpec
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.internal.DummyClusterSpec
import org.assertj.core.api.Assertions.assertThat
import org.junit.Ignore
import org.junit.Test
import rx.Observable
import java.util.*
@ -32,18 +32,23 @@ class DistributedServiceTests {
private lateinit var raftNotaryIdentity: Party
private lateinit var notaryStateMachines: Observable<Pair<Party, StateMachineUpdate>>
private fun setup(testBlock: () -> Unit) {
private fun setup(compositeIdentity: Boolean = false, testBlock: () -> Unit) {
val testUser = User("test", "test", permissions = setOf(
startFlow<CashIssueFlow>(),
startFlow<CashPaymentFlow>(),
invokeRpc(CordaRPCOps::nodeInfo),
invokeRpc(CordaRPCOps::stateMachinesFeed))
)
driver(
extraCordappPackagesToScan = listOf("net.corda.finance.contracts"),
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, rpcUsers = listOf(testUser), cluster = ClusterSpec.Raft(clusterSize = 3))))
{
notarySpecs = listOf(
NotarySpec(
DUMMY_NOTARY_NAME,
rpcUsers = listOf(testUser),
cluster = DummyClusterSpec(clusterSize = 3, compositeServiceIdentity = compositeIdentity))
),
portAllocation = PortAllocation.RandomFree
) {
alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(testUser)).getOrThrow()
raftNotaryIdentity = defaultNotaryIdentity
notaryNodes = defaultNotaryHandle.nodeHandles.getOrThrow().map { it as NodeHandle.OutOfProcess }
@ -72,11 +77,60 @@ class DistributedServiceTests {
}
}
// TODO This should be in RaftNotaryServiceTests
@Test
fun `cluster survives if a notary is killed`() {
setup {
// Issue 100 pounds, then pay ourselves 10x5 pounds
issueCash(100.POUNDS)
for (i in 1..10) {
paySelf(5.POUNDS)
}
// Now kill a notary node
with(notaryNodes[0].process) {
destroy()
waitFor()
}
// Pay ourselves another 20x5 pounds
for (i in 1..20) {
paySelf(5.POUNDS)
}
val notarisationsPerNotary = HashMap<Party, Int>()
notaryStateMachines.expectEvents(isStrict = false) {
replicate<Pair<Party, StateMachineUpdate>>(30) {
expect(match = { it.second is StateMachineUpdate.Added }) { (notary, update) ->
update as StateMachineUpdate.Added
notarisationsPerNotary.compute(notary) { _, number -> number?.plus(1) ?: 1 }
}
}
}
println("Notarisation distribution: $notarisationsPerNotary")
require(notarisationsPerNotary.size == 3)
}
}
// TODO Use a dummy distributed service rather than a Raft Notary Service as this test is only about Artemis' ability
// to handle distributed services
@Ignore("Test has undeterministic capacity to hang, ignore till fixed")
@Test
fun `requests are distributed evenly amongst the nodes`() = setup {
fun `requests are distributed evenly amongst the nodes`() {
setup {
checkRequestsDistributedEvenly()
}
}
@Test
fun `requests are distributed evenly amongst the nodes with a composite public key`() {
setup(true) {
checkRequestsDistributedEvenly()
}
}
private fun checkRequestsDistributedEvenly() {
// Issue 100 pounds, then pay ourselves 50x2 pounds
issueCash(100.POUNDS)
@ -102,42 +156,6 @@ class DistributedServiceTests {
require(notarisationsPerNotary.values.all { it > 10 })
}
// TODO This should be in RaftNotaryServiceTests
@Ignore("Test has undeterministic capacity to hang, ignore till fixed")
@Test
fun `cluster survives if a notary is killed`() = setup {
// Issue 100 pounds, then pay ourselves 10x5 pounds
issueCash(100.POUNDS)
for (i in 1..10) {
paySelf(5.POUNDS)
}
// Now kill a notary node
with(notaryNodes[0].process) {
destroy()
waitFor()
}
// Pay ourselves another 20x5 pounds
for (i in 1..20) {
paySelf(5.POUNDS)
}
val notarisationsPerNotary = HashMap<Party, Int>()
notaryStateMachines.expectEvents(isStrict = false) {
replicate<Pair<Party, StateMachineUpdate>>(30) {
expect(match = { it.second is StateMachineUpdate.Added }) { (notary, update) ->
update as StateMachineUpdate.Added
notarisationsPerNotary.compute(notary) { _, number -> number?.plus(1) ?: 1 }
}
}
}
println("Notarisation distribution: $notarisationsPerNotary")
require(notarisationsPerNotary.size == 3)
}
private fun issueCash(amount: Amount<Currency>) {
aliceProxy.startFlow(::CashIssueFlow, amount, OpaqueBytes.of(0), raftNotaryIdentity).returnValue.getOrThrow()
}

View File

@ -173,11 +173,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
}
private inline fun signNodeInfo(nodeInfo: NodeInfo, sign: (PublicKey, SerializedBytes<NodeInfo>) -> DigitalSignature): SignedNodeInfo {
// For now we assume the node has only one identity (excluding any composite ones)
val owningKey = nodeInfo.legalIdentities.single { it.owningKey !is CompositeKey }.owningKey
// For now we exclude any composite identities, see [SignedNodeInfo]
val owningKeys = nodeInfo.legalIdentities.map { it.owningKey }.filter { it !is CompositeKey }
val serialised = nodeInfo.serialize()
val signature = sign(owningKey, serialised)
return SignedNodeInfo(serialised, listOf(signature))
val signatures = owningKeys.map { sign(it, serialised) }
return SignedNodeInfo(serialised, signatures)
}
open fun generateAndSaveNodeInfo(): NodeInfo {