Change references to 'wallet' with 'vault'

This commit is contained in:
Ross Nicoll
2016-09-19 11:32:38 +01:00
parent ae4513c8eb
commit ebda724f14
38 changed files with 252 additions and 250 deletions

View File

@ -220,18 +220,18 @@ data class ScheduledActivity(val logicRef: ProtocolLogicRef, override val schedu
/**
* A state that evolves by superseding itself, all of which share the common "linearId".
*
* This simplifies the job of tracking the current version of certain types of state in e.g. a wallet.
* This simplifies the job of tracking the current version of certain types of state in e.g. a vault.
*/
interface LinearState: ContractState {
/**
* Unique id shared by all LinearState states throughout history within the wallets of all parties.
* Unique id shared by all LinearState states throughout history within the vaults of all parties.
* Verify methods should check that one input and one output share the id in a transaction,
* except at issuance/termination.
*/
val linearId: UniqueIdentifier
/**
* True if this should be tracked by our wallet(s).
* True if this should be tracked by our vault(s).
* */
fun isRelevant(ourKeys: Set<PublicKey>): Boolean
@ -328,7 +328,7 @@ data class StateRef(val txhash: SecureHash, val index: Int) {
override fun toString() = "$txhash($index)"
}
/** A StateAndRef is simply a (state, ref) pair. For instance, a wallet (which holds available assets) contains these. */
/** A StateAndRef is simply a (state, ref) pair. For instance, a vault (which holds available assets) contains these. */
data class StateAndRef<out T : ContractState>(val state: TransactionState<T>, val ref: StateRef)
/** Filters a list of [StateAndRef] objects according to the type of the states */

View File

@ -31,7 +31,7 @@ data class TransactionForContract(val inputs: List<ContractState>,
* The purpose of this function is to simplify the writing of verification logic for transactions that may contain
* similar but unrelated state evolutions which need to be checked independently. Consider a transaction that
* simultaneously moves both dollars and euros (e.g. is an atomic FX trade). There may be multiple dollar inputs and
* multiple dollar outputs, depending on things like how fragmented the owners wallet is and whether various privacy
* multiple dollar outputs, depending on things like how fragmented the owner's vault is and whether various privacy
* techniques are in use. The quantity of dollars on the output side must sum to the same as on the input side, to
* ensure no money is being lost track of. This summation and checking must be repeated independently for each
* currency. To solve this, you would use groupStates with a type of Cash.State and a selector that returns the

View File

@ -19,7 +19,7 @@ import java.time.Clock
* state from being serialized in checkpoints.
*/
interface ServiceHub {
val walletService: WalletService
val vaultService: VaultService
val keyManagementService: KeyManagementService
val identityService: IdentityService
val storageService: StorageService
@ -30,7 +30,7 @@ interface ServiceHub {
/**
* Given a list of [SignedTransaction]s, writes them to the local storage for validated transactions and then
* sends them to the wallet for further processing.
* sends them to the vault for further processing.
*
* @param txs The transactions to record.
*/
@ -38,7 +38,7 @@ interface ServiceHub {
/**
* Given some [SignedTransaction]s, writes them to the local storage for validated transactions and then
* sends them to the wallet for further processing.
* sends them to the vault for further processing.
*
* @param txs The transactions to record.
*/

View File

@ -21,9 +21,9 @@ val DEFAULT_SESSION_ID = 0L
*/
/**
* A wallet (name may be temporary) wraps a set of states that are useful for us to keep track of, for instance,
* because we own them. This class represents an immutable, stable state of a wallet: it is guaranteed not to
* change out from underneath you, even though the canonical currently-best-known wallet may change as we learn
* A vault (name may be temporary) wraps a set of states that are useful for us to keep track of, for instance,
* because we own them. This class represents an immutable, stable state of a vault: it is guaranteed not to
* change out from underneath you, even though the canonical currently-best-known vault may change as we learn
* about new transactions from our peers and generate new transactions that consume states ourselves.
*
* This abstract class has no references to Cash contracts.
@ -32,16 +32,16 @@ val DEFAULT_SESSION_ID = 0L
* Active means they haven't been consumed yet (or we don't know about it).
* Relevant means they contain at least one of our pubkeys.
*/
class Wallet(val states: Iterable<StateAndRef<ContractState>>) {
class Vault(val states: Iterable<StateAndRef<ContractState>>) {
@Suppress("UNCHECKED_CAST")
inline fun <reified T : ContractState> statesOfType() = states.filter { it.state.data is T } as List<StateAndRef<T>>
/**
* Represents an update observed by the Wallet that will be notified to observers. Include the [StateRef]s of
* Represents an update observed by the vault that will be notified to observers. Include the [StateRef]s of
* transaction outputs that were consumed (inputs) and the [ContractState]s produced (outputs) to/by the transaction
* or transactions observed and the Wallet.
* or transactions observed and the vault.
*
* If the Wallet observes multiple transactions simultaneously, where some transactions consume the outputs of some of the
* If the vault observes multiple transactions simultaneously, where some transactions consume the outputs of some of the
* other transactions observed, then the changes are observed "net" of those.
*/
data class Update(val consumed: Set<StateRef>, val produced: Set<StateAndRef<ContractState>>) {
@ -54,7 +54,7 @@ class Wallet(val states: Iterable<StateAndRef<ContractState>>) {
operator fun plus(rhs: Update): Update {
val previouslyProduced = produced.map { it.ref }
val previouslyConsumed = consumed
val combined = Wallet.Update(
val combined = Vault.Update(
previouslyConsumed + (rhs.consumed - previouslyProduced),
// The ordering below matters to preserve ordering of consumed/produced Sets when they are insertion order dependent implementations.
produced.filter { it.ref !in rhs.consumed }.toSet() + rhs.produced)
@ -79,19 +79,19 @@ class Wallet(val states: Iterable<StateAndRef<ContractState>>) {
}
/**
* A [WalletService] is responsible for securely and safely persisting the current state of a wallet to storage. The
* wallet service vends immutable snapshots of the current wallet for working with: if you build a transaction based
* on a wallet that isn't current, be aware that it may end up being invalid if the states that were used have been
* A [VaultService] is responsible for securely and safely persisting the current state of a vault to storage. The
* vault service vends immutable snapshots of the current vault for working with: if you build a transaction based
* on a vault that isn't current, be aware that it may end up being invalid if the states that were used have been
* consumed by someone else first!
*
* Note that transactions we've seen are held by the storage service, not the wallet.
* Note that transactions we've seen are held by the storage service, not the vault.
*/
interface WalletService {
interface VaultService {
/**
* Returns a read-only snapshot of the wallet at the time the call is made. Note that if you consume states or
* keys in this wallet, you must inform the wallet service so it can update its internal state.
* Returns a read-only snapshot of the vault at the time the call is made. Note that if you consume states or
* keys in this vault, you must inform the vault service so it can update its internal state.
*/
val currentWallet: Wallet
val currentVault: Vault
/**
* Returns a snapshot of the heads of LinearStates.
@ -107,34 +107,34 @@ interface WalletService {
}
fun statesForRefs(refs: List<StateRef>): Map<StateRef, TransactionState<*>?> {
val refsToStates = currentWallet.states.associateBy { it.ref }
val refsToStates = currentVault.states.associateBy { it.ref }
return refs.associateBy({ it }, { refsToStates[it]?.state })
}
/**
* Possibly update the wallet by marking as spent states that these transactions consume, and adding any relevant
* Possibly update the vault by marking as spent states that these transactions consume, and adding any relevant
* new states that they create. You should only insert transactions that have been successfully verified here!
*
* Returns the new wallet that resulted from applying the transactions (note: it may quickly become out of date).
* Returns the new vault that resulted from applying the transactions (note: it may quickly become out of date).
*
* TODO: Consider if there's a good way to enforce the must-be-verified requirement in the type system.
*/
fun notifyAll(txns: Iterable<WireTransaction>): Wallet
fun notifyAll(txns: Iterable<WireTransaction>): Vault
/** Same as notifyAll but with a single transaction. */
fun notify(tx: WireTransaction): Wallet = notifyAll(listOf(tx))
fun notify(tx: WireTransaction): Vault = notifyAll(listOf(tx))
/**
* Get a synchronous Observable of updates. When observations are pushed to the Observer, the Wallet will already incorporate
* the update.
* Get a synchronous Observable of updates. When observations are pushed to the Observer, the vault will already
* incorporate the update.
*/
val updates: rx.Observable<Wallet.Update>
val updates: rx.Observable<Vault.Update>
/**
* Provide a [Future] for when a [StateRef] is consumed, which can be very useful in building tests.
*/
fun whenConsumed(ref: StateRef): ListenableFuture<Wallet.Update> {
val future = SettableFuture.create<Wallet.Update>()
fun whenConsumed(ref: StateRef): ListenableFuture<Vault.Update> {
val future = SettableFuture.create<Vault.Update>()
updates.filter { ref in it.consumed }.first().subscribe {
future.set(it)
}
@ -142,7 +142,7 @@ interface WalletService {
}
}
inline fun <reified T : LinearState> WalletService.linearHeadsOfType() = linearHeadsOfType_(T::class.java)
inline fun <reified T : LinearState> VaultService.linearHeadsOfType() = linearHeadsOfType_(T::class.java)
/**
* The KMS is responsible for storing and using private keys to sign things. An implementation of this may, for example,
@ -206,7 +206,7 @@ interface TxWritableStorageService : StorageService {
*
* If the point in time is in the past, the expectation is that the activity will happen shortly after it is scheduled.
*
* The main consumer initially is an observer of the wallet to schedule activities based on transactions as they are
* The main consumer initially is an observer of the vault to schedule activities based on transactions as they are
* recorded.
*/
interface SchedulerService {

View File

@ -13,8 +13,8 @@ interface ReadOnlyTransactionStorage {
fun getTransaction(id: SecureHash): SignedTransaction?
/**
* Get a synchronous Observable of updates. When observations are pushed to the Observer, the Wallet will already incorporate
* the update.
* Get a synchronous Observable of updates. When observations are pushed to the Observer, the vault will already
* incorporate the update.
*/
val updates: rx.Observable<SignedTransaction>
}

View File

@ -4,8 +4,8 @@ import com.r3corda.core.ThreadBox
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.node.ServiceHub
import com.r3corda.core.node.services.Wallet
import com.r3corda.core.node.services.WalletService
import com.r3corda.core.node.services.Vault
import com.r3corda.core.node.services.VaultService
import com.r3corda.core.serialization.SingletonSerializeAsToken
import com.r3corda.core.transactions.WireTransaction
import com.r3corda.core.utilities.loggerFor
@ -17,88 +17,88 @@ import java.util.*
import javax.annotation.concurrent.ThreadSafe
/**
* This class implements a simple, in memory wallet that tracks states that are owned by us, and also has a convenience
* method to auto-generate some self-issued cash states that can be used for test trading. A real wallet would persist
* states relevant to us into a database and once such a wallet is implemented, this scaffolding can be removed.
* This class implements a simple, in memory vault that tracks states that are owned by us, and also has a convenience
* method to auto-generate some self-issued cash states that can be used for test trading. A real vault would persist
* states relevant to us into a database and once such a vault is implemented, this scaffolding can be removed.
*/
@ThreadSafe
open class InMemoryWalletService(protected val services: ServiceHub) : SingletonSerializeAsToken(), WalletService {
open protected val log = loggerFor<InMemoryWalletService>()
open class InMemoryVaultService(protected val services: ServiceHub) : SingletonSerializeAsToken(), VaultService {
open protected val log = loggerFor<InMemoryVaultService>()
// Variables inside InnerState are protected with a lock by the ThreadBox and aren't in scope unless you're
// inside mutex.locked {} code block. So we can't forget to take the lock unless we accidentally leak a reference
// to wallet somewhere.
// to vault somewhere.
protected class InnerState {
var wallet = Wallet(emptyList<StateAndRef<ContractState>>())
var vault = Vault(emptyList<StateAndRef<ContractState>>())
}
protected val mutex = ThreadBox(InnerState())
override val currentWallet: Wallet get() = mutex.locked { wallet }
override val currentVault: Vault get() = mutex.locked { vault }
private val _updatesPublisher = PublishSubject.create<Wallet.Update>()
private val _updatesPublisher = PublishSubject.create<Vault.Update>()
override val updates: Observable<Wallet.Update>
override val updates: Observable<Vault.Update>
get() = _updatesPublisher
/**
* Returns a snapshot of the heads of LinearStates.
*/
override val linearHeads: Map<UniqueIdentifier, StateAndRef<LinearState>>
get() = currentWallet.let { wallet ->
wallet.states.filterStatesOfType<LinearState>().associateBy { it.state.data.linearId }.mapValues { it.value }
get() = currentVault.let { vault ->
vault.states.filterStatesOfType<LinearState>().associateBy { it.state.data.linearId }.mapValues { it.value }
}
override fun notifyAll(txns: Iterable<WireTransaction>): Wallet {
override fun notifyAll(txns: Iterable<WireTransaction>): Vault {
val ourKeys = services.keyManagementService.keys.keys
// Note how terribly incomplete this all is!
//
// - We don't notify anyone of anything, there are no event listeners.
// - We don't handle or even notice invalidations due to double spends of things in our wallet.
// - We don't handle or even notice invalidations due to double spends of things in our vault.
// - We have no concept of confidence (for txns where there is no definite finality).
// - No notification that keys are used, for the case where we observe a spend of our own states.
// - No ability to create complex spends.
// - No logging or tracking of how the wallet got into this state.
// - No logging or tracking of how the vault got into this state.
// - No persistence.
// - Does tx relevancy calculation and key management need to be interlocked? Probably yes.
//
// ... and many other things .... (Wallet.java in bitcoinj is several thousand lines long)
var netDelta = Wallet.NoUpdate
val changedWallet = mutex.locked {
// Starting from the current wallet, keep applying the transaction updates, calculating a new Wallet each
var netDelta = Vault.NoUpdate
val changedVault = mutex.locked {
// Starting from the current vault, keep applying the transaction updates, calculating a new vault each
// time, until we get to the result (this is perhaps a bit inefficient, but it's functional and easily
// unit tested).
val walletAndNetDelta = txns.fold(Pair(currentWallet, Wallet.NoUpdate)) { walletAndDelta, tx ->
val (wallet, delta) = walletAndDelta.first.update(tx, ourKeys)
val combinedDelta = delta + walletAndDelta.second
Pair(wallet, combinedDelta)
val vaultAndNetDelta = txns.fold(Pair(currentVault, Vault.NoUpdate)) { vaultAndDelta, tx ->
val (vault, delta) = vaultAndDelta.first.update(tx, ourKeys)
val combinedDelta = delta + vaultAndDelta.second
Pair(vault, combinedDelta)
}
wallet = walletAndNetDelta.first
netDelta = walletAndNetDelta.second
return@locked wallet
vault = vaultAndNetDelta.first
netDelta = vaultAndNetDelta.second
return@locked vault
}
if (netDelta != Wallet.NoUpdate) {
if (netDelta != Vault.NoUpdate) {
_updatesPublisher.onNext(netDelta)
}
return changedWallet
return changedVault
}
private fun isRelevant(state: ContractState, ourKeys: Set<PublicKey>): Boolean {
return if (state is OwnableState) {
state.owner in ourKeys
} else if (state is LinearState) {
// It's potentially of interest to the wallet
// It's potentially of interest to the vault
state.isRelevant(ourKeys)
} else {
false
}
}
private fun Wallet.update(tx: WireTransaction, ourKeys: Set<PublicKey>): Pair<Wallet, Wallet.Update> {
private fun Vault.update(tx: WireTransaction, ourKeys: Set<PublicKey>): Pair<Vault, Vault.Update> {
val ourNewStates = tx.outputs.
filter { isRelevant(it.data, ourKeys) }.
map { tx.outRef<ContractState>(it.data) }
@ -108,19 +108,19 @@ open class InMemoryWalletService(protected val services: ServiceHub) : Singleton
// Is transaction irrelevant?
if (consumed.isEmpty() && ourNewStates.isEmpty()) {
log.trace { "tx ${tx.id} was irrelevant to this wallet, ignoring" }
return Pair(this, Wallet.NoUpdate)
log.trace { "tx ${tx.id} was irrelevant to this vault, ignoring" }
return Pair(this, Vault.NoUpdate)
}
val change = Wallet.Update(consumed, HashSet(ourNewStates))
val change = Vault.Update(consumed, HashSet(ourNewStates))
// And calculate the new wallet.
// And calculate the new vault.
val newStates = states.filter { it.ref !in consumed } + ourNewStates
log.trace {
"Applied tx ${tx.id.prefixChars()} to the wallet: consumed ${consumed.size} states and added ${newStates.size}"
"Applied tx ${tx.id.prefixChars()} to the vault: consumed ${consumed.size} states and added ${newStates.size}"
}
return Pair(Wallet(newStates), change)
return Pair(Vault(newStates), change)
}
}

View File

@ -2,14 +2,14 @@ package com.r3corda.core.node
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.node.services.Wallet
import com.r3corda.core.node.services.Vault
import com.r3corda.core.utilities.DUMMY_NOTARY
import org.junit.Test
import java.security.PublicKey
import kotlin.test.assertEquals
class WalletUpdateTests {
class VaultUpdateTests {
object DummyContract : Contract {
@ -22,7 +22,7 @@ class WalletUpdateTests {
private class DummyState : ContractState {
override val participants: List<PublicKey>
get() = emptyList()
override val contract = WalletUpdateTests.DummyContract
override val contract = VaultUpdateTests.DummyContract
}
private val stateRef0 = StateRef(SecureHash.randomSHA256(), 0)
@ -39,47 +39,47 @@ class WalletUpdateTests {
@Test
fun `nothing plus nothing is nothing`() {
val before = Wallet.NoUpdate
val after = before + Wallet.NoUpdate
val before = Vault.NoUpdate
val after = before + Vault.NoUpdate
assertEquals(before, after)
}
@Test
fun `something plus nothing is something`() {
val before = Wallet.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef2, stateAndRef3))
val after = before + Wallet.NoUpdate
val before = Vault.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef2, stateAndRef3))
val after = before + Vault.NoUpdate
assertEquals(before, after)
}
@Test
fun `nothing plus something is something`() {
val before = Wallet.NoUpdate
val after = before + Wallet.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef2, stateAndRef3))
val expected = Wallet.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef2, stateAndRef3))
val before = Vault.NoUpdate
val after = before + Vault.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef2, stateAndRef3))
val expected = Vault.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef2, stateAndRef3))
assertEquals(expected, after)
}
@Test
fun `something plus consume state 0 is something without state 0 output`() {
val before = Wallet.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1))
val after = before + Wallet.Update(setOf(stateRef0), setOf())
val expected = Wallet.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef1))
val before = Vault.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1))
val after = before + Vault.Update(setOf(stateRef0), setOf())
val expected = Vault.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef1))
assertEquals(expected, after)
}
@Test
fun `something plus produce state 4 is something with additional state 4 output`() {
val before = Wallet.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1))
val after = before + Wallet.Update(setOf(), setOf(stateAndRef4))
val expected = Wallet.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1, stateAndRef4))
val before = Vault.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1))
val after = before + Vault.Update(setOf(), setOf(stateAndRef4))
val expected = Vault.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1, stateAndRef4))
assertEquals(expected, after)
}
@Test
fun `something plus consume states 0 and 1, and produce state 4, is something without state 0 and 1 outputs and only state 4 output`() {
val before = Wallet.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1))
val after = before + Wallet.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef4))
val expected = Wallet.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef4))
val before = Vault.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1))
val after = before + Vault.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef4))
val expected = Vault.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef4))
assertEquals(expected, after)
}
}