From 7159dfcb6121cb29fb8c605d65989f3abb144dc9 Mon Sep 17 00:00:00 2001 From: James Brown <33660060+jamesbr3@users.noreply.github.com> Date: Fri, 21 Sep 2018 18:48:47 +0100 Subject: [PATCH 1/2] CORDA-1787 Bring threat model into the docsite (#3817) * CORDA-1787 bring threat model into docs site * CORDA-1787 updating with full text * CORDA-1787 update/fix typos * CORDA-1787 update threat model with PR feedback * CORDA-1787 fix typos * CORDA-1787 move threat model into design section * CORDA-1787 update formatting using pure markdown --- .../design/threat-model/corda-threat-model.md | 429 ++++++++++++++++++ .../threat-model/images/threat-model.png | Bin 0 -> 23848 bytes docs/source/index.rst | 1 + 3 files changed, 430 insertions(+) create mode 100644 docs/source/design/threat-model/corda-threat-model.md create mode 100644 docs/source/design/threat-model/images/threat-model.png diff --git a/docs/source/design/threat-model/corda-threat-model.md b/docs/source/design/threat-model/corda-threat-model.md new file mode 100644 index 0000000000..83e5a1da1f --- /dev/null +++ b/docs/source/design/threat-model/corda-threat-model.md @@ -0,0 +1,429 @@ + +Corda Threat Model +================== + +This document describes the security threat model of the Corda Platform. The Corda Threat Model is the result of architectural and threat modelling sessions, +and is designed to provide a high level overview of the security objectives for the Corda Network , and the controls and mitigations used to deliver on those +objectives. It is intended to support subsequent analysis and architecture of systems connecting with the network and the applications which interact with data +across it. + +It is incumbent on all ledger network participants to review and assess the security measures described in this document against their specific organisational +requirements and policies, and to implement any additional measures needed. + +Scope +----- + +Built on the [Corda](http://www.corda.net/) distributed ledger platform designed by R3, the ledger network enables the origination and management of agreements +between business partners. Participants to the network create and maintain Corda *nodes,* each hosting one or more pluggable applications ( *CorDapps* ) which +define the data to be exchanged and its workflow. See the [Corda Technical White Paper](https://docs.corda.net/_static/corda-technical-whitepaper.pdf) for a +detailed description of Corda's design and functionality. + +R3 provide and maintain a number of essential services underpinning the ledger network. In the future these services are intended to be operated by a separate +Corda Foundation. The network services currently include: + +- Network Identity service ('Doorman'): Issues signed digital certificates that uniquely identity parties on the network. +- Network Map service: Provides a way for nodes to advertise their identity, and identify other nodes on the network, their network address and advertised + services. + +Participants to the ledger network include major institutions, financial organisations and regulated bodies, across various global jurisdictions. In a majority +of cases, there are stringent requirements in place for participants to demonstrate that their handling of all data is performed in an appropriately secure +manner, including the exchange of data over the ledger network. This document identifies measures within the Corda platform and supporting infrastructure to +mitigate key security risks in support of these requirements. + +The Corda Network +----------------- + +The diagram below illustrates the network architecture, protocols and high level data flows that comprise the Corda Network. The threat model has been developed +based upon this architecture. + +![](./images/threat-model.png) + +Threat Model +------------ + +Threat Modelling is an iterative process that works to identify, describe and mitigate threats to a system. One of the most common models for identifying +threats is the [STRIDE](https://en.wikipedia.org/wiki/STRIDE_(security)) framework. It provides a set of security threats in six categories: + +- Spoofing +- Tampering +- Information Disclosure +- Repudiation +- Denial of Service +- Elevation of Privilege + +The Corda threat model uses the STRIDE framework to present the threats to the Corda Network in a structured way. It should be stressed that threat modelling is +an iterative process that is never complete. The model described below is part of an on-going process intended to continually refine the security architecture +of the Corda platform. + +### Spoofing + +Spoofing is pretending to be something or someone other than yourself. It is the actions taken by an attacker to impersonate another party, typically for the +purposes of gaining unauthorised access to privileged data, or perpetrating fraudulent transactions. Spoofing can occur on multiple levels. Machines can be +impersonated at the network level by a variety of methods such as ARP & IP spoofing or DNS compromise. + +Spoofing can also occur at an application or user-level. Attacks at this level typically target authentication logic, using compromised passwords and +cryptographic keys, or by subverting cryptography systems. + +Corda employs a Public Key Infrastructure (PKI) to validate the identity of nodes, both at the point of registration with the network map service and +subsequently through the cryptographic signing of transactions. An imposter would need to acquire an organisation's private keys in order to meaningfully +impersonate that organisation. R3 provides guidance to all ledger network participants to ensure adequate security is maintained around cryptographic keys. + ++-------------+------------------------------------------------------------------------------+----------------------------------------------------------------+ +| Element | Attacks | Mitigations | ++=============+==============================================================================+================================================================+ +| RPC Client | An external attacker impersonates an RPC client and is able to initiate | The RPC Client is authenticated by the node and must supply | +| | flows on their behalf. | valid credentials (username & password). | +| | | | +| | A malicious RPC client connects to the node and impersonates another, | RPC Client permissions are configured by the node | +| | higher-privileged client on the same system, and initiates flows on their | administrator and can be used to restrict the actions and | +| | behalf. | flows available to the client. | +| | | | +| | **Impacts** | RPC credentials and permissions can be managed by an Apache | +| | | Shiro service. The RPC service restricts which actions are | +| | If successful, the attacker would be able to perform actions that they are | available to a client based on what permissions they have been | +| | not authorised to perform, such initiating flows. The impact of these | assigned. | +| | actions could have financial consequences depending on what flows were | | +| | available to the attacker. | | ++-------------+------------------------------------------------------------------------------+----------------------------------------------------------------+ +| Node | An attacker attempts to impersonate a node and issue a transaction using | Nodes must connect to each other using using | +| | their identity. | mutually-authenticated TLS connections. Node identity is | +| | | authenticated using the certificates exchanged as part of the | +| | An attacker attempts to impersonate another node on the network by | TLS protocol. Only the node that owns the corresponding | +| | submitting NodeInfo updates with falsified address and/or identity | private key can assert their true identity. | +| | information. | | +| | | NodeInfo updates contain the node's public identity | +| | **Impacts** | certificate and must be signed by the corresponding private | +| | | key. Only the node in possession of this private key can sign | +| | If successful, a node able to assume the identity of another party could | the NodeInfo. | +| | conduct fraudulent transactions (e.g. pay cash to its own identity), giving | | +| | a direct financial impact to the compromised identity. Demonstrating that | Corda employs a Public Key Infrastructure (PKI) to validate | +| | the actions were undertaken fraudulently could prove technically challenging | the identity of nodes. An imposter would need to acquire an | +| | to any subsequent dispute resolution process. | organisation's private keys in order to meaningfully | +| | | impersonate that organisation. Corda will soon support a range | +| | In addition, an impersonating node may be able to obtain privileged | of HSMs (Hardware Security Modules) for storing a node's | +| | information from other nodes, including receipt of messages intended for the | private keys, which mitigates this risk. | +| | original party containing information on new and historic transactions. | | ++-------------+------------------------------------------------------------------------------+----------------------------------------------------------------+ +| Network Map | An attacker with appropriate network access performs a DNS compromise, | Connections to the Network Map service are secured using the | +| | resulting in network traffic to the Doorman & Network Map being routed to | HTTPS protocol. The connecting node authenticates the | +| | their attack server, which attempts to impersonate these machines. | NetworkMap servers using their public certificates, to ensure | +| | | the identity of these servers is correct. | +| | **Impact** | | +| | | All data received from the NetworkMap is digitally signed (in | +| | Impersonation of the Network Map would enable an attacker to issue | addition to being protected by TLS) - an attacker attempting | +| | unauthorised updates to the map. | to spoof the Network Map would need to acquire both private | +| | | TLS keys, and the private NetworkMap signing keys. | +| | | | +| | | The Doorman and NetworkMap signing keys are stored inside a | +| | | (Hardware Security Module (HSM) with strict security controls | +| | | (network separation and physical access controls). | ++-------------+------------------------------------------------------------------------------+----------------------------------------------------------------+ +| Doorman | An malicious attacker operator attempts to join the Corda Network by | R3 operate strict validation procedures to ensure that | +| | impersonating an existing organisation and issues a fraudulent registration | requests to join the Corda Network have legitimately | +| | request. | originated from the organisation in question. | +| | | | +| | **Impact** | | +| | | | +| | The attacker would be able to join and impersonate an organisation. | | +| | | | +| | The operator could issue an identity cert for any organisation, publish a | | +| | valid NodeInfo and redirect all traffic to themselves in the clear. | | ++-------------+------------------------------------------------------------------------------+----------------------------------------------------------------+ + + + +### Tampering + +Tampering refers to the modification of data with malicious intent. This typically involves modification of data at rest (such as a file on disk, or fields in a +database), or modification of data in transit. + +To be successful, an attacker would require privileged access to some part of the network infrastructure (either public or internal private networks). They +might also have access to a node's file-system, database or even direct memory access. + ++------------+-----------------------------------------------------------------------------+------------------------------------------------------------------+ +| Element | Attacks | Mitigations | ++============+=============================================================================+==================================================================+ +| Node | Unintended, adverse behaviour of a CorDapp running on one or more nodes - | By design, Corda's notary-based consensus model and contract | +| (CorDapp) | either its core code or any supporting third party libraries. A coding bug | validation mechanisms provide protection against attempts to | +| | is assumed to be the default cause, although malicious modification of a | alter shared data or perform invariant operations. The primary | +| | CorDapp could result in similar effects. | risk is therefore to local systems. | +| | | | +| | | Future versions of Corda will require CorDapps to be executed | +| | | inside a sandboxed JVM environment, modified to restrict | +| | | unauthorised access to the local file system and network. This | +| | | is intended to minimise the potential of a compromised CorDapp | +| | | to affect systems local to the node. | ++------------+-----------------------------------------------------------------------------+------------------------------------------------------------------+ +| P2P & RPC | An attacker performs Man-in-the-Middle (MITM) attack against a node's | Mutually authenticated TLS connections between nodes ensures | +| connection | peer-to-peer (P2P) connection | that Man-In-The-Middle (MITM) attacks cannot take place. Corda | +| s | | Nodes restrict their connections to TLS v1.2 and also restrict | +| | **Impact** | which cipher suites are accepted. | +| | | | +| | An attacker would be able to modify transactions between participating | | +| | nodes. | | ++------------+-----------------------------------------------------------------------------+------------------------------------------------------------------+ +| Node Vault | An attacker gains access to the node's vault and modifies tables in the | There are not currently any direct controls to mitigate this | +| | database. | kind of attack. A node's vault is assumed to be within the same | +| | | trust boundary of the node JVM. Access to the vault must be | +| | **Impact** | restricted such that only the node can access it. Both | +| | | network-level controls (fire-walling) and database permissions | +| | Transaction history would become compromised. The impact could range from | must be employed. | +| | deletion of data to malicious tampering of financial detail. | | +| | | Note that the tampering of a node's vault only affects that | +| | | specific node's transaction history. No other node in the | +| | | network is affected and any tampering attempts are easily | +| | | detected. | +| | | | +| | | | ++------------+-----------------------------------------------------------------------------+------------------------------------------------------------------+ +| Network | An attacker compromises the Network Map service and publishes an | Individual Node entries in the NetworkMap must be signed by the | +| Map | illegitimate update. | associated node's private key. The signatures are validated by | +| | | the NetworkMap service, and all other Nodes in the network, to | +| | **Impact** | ensure they have not been tampered with. An attacker would need | +| | | to acquire a node's private identity signing key to be able to | +| | NodeInfo entries (name & address information) could potentially become | make modifications to a NodeInfo. This is only possible if the | +| | altered if this attack was possible | attacker has control of the node in question. | +| | | | +| | The NetworkMap could be deleted and/or unauthorized nodes could be added | It is not possible for the NetworkMap service (or R3) to modify | +| | to, or removed from the map. | entries in the network map (because the node's private keys are | +| | | not accessible). If the NetworkMap service were compromised, the | +| | | only impact the attacker could have would be to add or remove | +| | | individual entries in the map. | ++------------+-----------------------------------------------------------------------------+------------------------------------------------------------------+ + +### Repudiation + +Repudiation refers to the ability to claim a malicious action did not take place. Repudiation becomes relevant when it is not possible to verify the identity of +an attacker, or there is a lack of evidence to link their malicious actions with events in a system. + +Preventing repudiation does not prevent other forms of attack. Rather, the goal is to ensure that the attacker is identifiable, their actions can be traced, and +there is no way for the attacker to deny having committed those actions. + ++-------------+------------------------------------------------------------------------------+-----------------------------------------------------------------+ +| Element | Attacks | Mitigations | ++=============+==============================================================================+=================================================================+ +| RPC Client | Attacker attempts to initiate a flow that they are not entitled to perform | RPC clients must authenticate to the Node using credentials | +| | | passed over TLS. It is therefore not possible for an RPC client | +| | **Impact** | to perform actions without first proving their identity. | +| | | | +| | Flows could be initiated without knowing the identity of the client. | All interactions with an RPC user are also logged by the node. | +| | | An attacker's identity and actions will be recorded and cannot | +| | | be repudiated. | ++-------------+------------------------------------------------------------------------------+-----------------------------------------------------------------+ +| Node | A malicious CorDapp attempts to spend a state that does not belong to them. | Corda transactions must be signed with a node's private | +| | The node operator then claims that it was not their node that initiated the | identity key in order to be accepted by the rest of the | +| | transaction. | network. The signature directly identities the signing party | +| | | and cannot be made by any other node - therefore the act of | +| | **Impact** | signing a transaction | +| | | | +| | Financial transactions could be initiated by anonymous parties, leading to | Corda transactions between nodes utilize the P2P protocol, | +| | financial loss, and loss of confidence in the network. | which requires a mutually authenticated TLS connection. It is | +| | | not possible for a node to issue transactions without having | +| | | it's identity authenticated by other nodes in the network. Node | +| | | identity and TLS certificates are issued via Corda Network | +| | | services, and use the Corda PKI (Public Key Infrastructure) for | +| | | authentication. | +| | | | +| | | All P2P transactions are logged by the node, meaning that any | +| | | interactions are recorded | ++-------------+------------------------------------------------------------------------------+-----------------------------------------------------------------+ +| Node | A node attempts to perform a denial-of-state attack. | Non-validating Notaries require a signature over every request, | +| | | therefore nobody can deny performing denial-of-state attack | +| | | because every transaction clearly identities the node that | +| | | initiated it. | ++-------------+------------------------------------------------------------------------------+-----------------------------------------------------------------+ +| Node | | | ++-------------+------------------------------------------------------------------------------+-----------------------------------------------------------------+ + + + +### Information Disclosure + +Information disclosure is about the unauthorised access of data. Attacks of this kind have an impact when confidential data is accessed. Typical examples of +attack include extracting secrets from a running process, and accessing confidential files on a file-system which have not been appropriately secured. +Interception of network communications between trusted parties can also lead to information disclosure. + +An attacker capable of intercepting network traffic from a Corda node would, at a minimum, be able to identify which other parties that node was interacting +with, along with relative frequency and volume of data being shared; this could be used to infer additional privileged information without the parties' +consent. All network communication of a Corda is encrypted using the TLS protocol (v1.2), using modern cryptography algorithms. + ++------------+------------------------------------------------------------------------------+------------------------------------------------------------------+ +| Element | Attack | Mitigations | ++============+==============================================================================+==================================================================+ +| Node | An attacker attempts to retrieve transaction history from a peer node in the | By design, Corda nodes do not globally broadcast transaction | +| | network, for which they have no legitimate right of access. | information to all participants in the network. | +| | | | +| | Corda nodes will, upon receipt of a request referencing a valid transaction | A node will not divulge arbitrary transactions to a peer unless | +| | hash, respond with the dependency graph of that transaction. One theoretical | that peer has been included in the transaction flow. A node only | +| | scenario is therefore that a participant is able to guess (or otherwise | divulges transaction history if the transaction being requested | +| | acquire by illicit means) the hash of a valid transaction, thereby being | is a descendant of a transaction that the node itself has | +| | able to acquire its content from another node. | previously shared as part of the current flow session. | +| | | | +| | **Impact** | The SGX integration feature currently envisaged for Corda will | +| | | implement CPU peer-to-peer encryption under which transaction | +| | If successful, an exploit of the form above could result in information | graphs are transmitted in an encrypted state and only decrypted | +| | private to specific participants being shared with one or more | within a secure enclave. Knowledge of a transaction hash will | +| | non-privileged parties. This may include market-sensitive information used | then be further rendered insufficient for a non-privileged party | +| | to derive competitive advantage. | to view the content of a transaction. | ++------------+------------------------------------------------------------------------------+------------------------------------------------------------------+ +| Node Vault | An unauthorised user attempts to access the node's vault | Access to the Vault uses standard JDBC authentication mechanism. | +| (database) | | Any user connecting to the vault must have permission to do so. | +| | **Impact** | | +| | | | +| | Access to the vault would reveal the full transaction history that the node | | +| | has taken part in. This may include financial information. | | ++------------+------------------------------------------------------------------------------+------------------------------------------------------------------+ +| Node | An attacker who gains access to the machine running the Node attempts to | Corda Nodes are designed to be executed using a designated | +| Process | read memory from the JVM process. | 'corda' system process, which other users and processes on the | +| (JVM) | | system do not have permission to access. | +| | An attacker with access the file-system attempts to read the node's | | +| | cryptographic key-store, containing the private identity keys. | The node's Java Key Store is encrypted using PKCS\#12 | +| | | encryption. In the future Corda will eventually store its keys | +| | **Impact** | in a HSM (Hardware Security Module). | +| | | | +| | An attacker would be able to read sensitive such as private identity keys. | | +| | The worst impact would be the ability to extract private keys from the JVM | | +| | process. | | +| | | | +| | | | ++------------+------------------------------------------------------------------------------+------------------------------------------------------------------+ +| RPC Client | Interception of RPC traffic between a client system and the node. | RPC communications are protected by the TLS protocol. | +| | | | +| | A malicious RPC client authenticates to a Node and attempts to query the | Permission to query a node's vault must be explicitly granted on | +| | transaction vault. | a per-user basis. It is recommended that RPC credentials and | +| | | permissions are managed in an Apache Shiro database. | +| | **Impact** | | +| | | | +| | An attacker would be able to see details of transactions shared between the | | +| | connected business systems and any transacting party. | | ++------------+------------------------------------------------------------------------------+------------------------------------------------------------------+ + + + +### Denial of Service + +Denial-of-service (DoS) attacks target the availability of a resource from its intended users. There are two anticipated targets of a DoS attack - network +participants (Corda Nodes) and network services (Doorman and the Network Map). DoS attacks occur by targeting the node or network services with a high +volume/frequency of requests, or by sending malformed requests. Typical DoS attacks leverage a botnet or other distributed group of systems (Distributed Denial +of Service, DDoS). A successful DoS attack may result in non-availability of targeted ledger network node(s)/service(s), both during the attack and thereafter +until normal service can be resumed. + +Communication over the ledger network is primarily peer-to-peer. Therefore the network as a whole is relatively resilient to DoS attacks. Notaries and oracles +will only communicate with peers in the network, so are protected from non-member-on-member application-level attack. + +Corda Network Services are protected by enterprise-grade DDoS detection and mitigation services. + ++------------+------------------------------------------------------------------------------+------------------------------------------------------------------+ +| Element | Attack | Mitigations | ++============+==============================================================================+==================================================================+ +| Node | An attacker control sends high volume of malformed transactions to a node. | P2P communcation is authenticated as part of the TLS protocol, | +| | | meaning that attackers must be part of the Corda network to | +| | **Impact** | launch an attack. | +| | | | +| | Nodes targeted by this attack could exhaust their processing & memory | Communication over the ledger network is primarily peer-to-peer, | +| | resources, or potentially cease responding to transactions. | the network as a whole is relatively resilient to DoS attacks, | +| | | the primary threat being to specific nodes or services. | +| | | | +| | | Note that there is no specific mitigation against DoS attacks at | +| | | the per-node level. DoS attacks by participants on other | +| | | participants will be expressly forbidden under the terms of the | +| | | ledger network's network agreement. Measures will be taken | +| | | against any ledger network participant found to have perpetrated | +| | | a DoS attack, including exclusion from the ledger network | +| | | network and potential litigation. As a result, the perceived | +| | | risk of a member-on-member attack is low and technical measures | +| | | are not considered under this threat model, although they may be | +| | | included in future iterations. | ++------------+------------------------------------------------------------------------------+------------------------------------------------------------------+ +| CorDapp | Unintended termination or other logical sequence (e.g. due to a coding bug | The network agreement will stipulate a default maximum allowable | +| | in either Corda or a CorDapp) by which a party is rendered unable to resolve | period time - the 'event horizon' - within which a party is | +| | a f low. The most likely results from another party failing to respond when | required to provide a valid response to any message sent to it | +| | required to do so under the terms of the agreed transaction protocol. | in the course of a flow. If that period is exceeded, the flow | +| | | will be considered to be cancelled and may be discontinued | +| | **Impact** | without prejudice by all parties. The event horizon may be | +| | | superseded by agreements between parties specifying other | +| | Depending on the nature of the flow, a party could be financially impacted | timeout periods, which may be encoded into flows under the Corda | +| | by failure to resolve a flow on an indefinite basis. For example, a party | flow framework. | +| | may be left in possession of a digital asset without the means to transfer | | +| | it to another party. | Additional measures may be taken under the agreement against | +| | | parties who repeatedly fail to meet their response obligations | +| | | under the network agreement. | ++------------+------------------------------------------------------------------------------+------------------------------------------------------------------+ +| Doorman | Attacker submits excessive registration requests to the Doorman service | Doorman is deployed behind a rate-limiting firewall. | +| | | | +| | | Doorman requests are validated and filtered to ensure malformed | +| | | requests are rejected. | ++------------+------------------------------------------------------------------------------+------------------------------------------------------------------+ +| Network | Attacker causes the network map service to become unavailable | Updates to the network map must be signed by participant nodes | +| Map | | and are authenticated before being processed. | +| | | | +| | | The network map is designed to be distributed by a CDN (Content | +| | | Delivery Network). This design leverages the architecture and | +| | | security controls of the CDN and is expected to be resilient to | +| | | DDoS (Distributed Denial of Service) attack. | +| | | | +| | | The Network Map is also cached locally by nodes on the network. | +| | | If the network map online service were temporarily unavailable, | +| | | the Corda network would not be affected. | +| | | | +| | | There is no requirement for the network map services to be | +| | | highly available in order for the ledger network to be | +| | | operational. Temporary non-availability of the network map | +| | | service may delay certification of new entrants to the network, | +| | | but will have no impact on existing participants. Similarly, the | +| | | network map will be cached by individual nodes once downloaded | +| | | from the network map service; unplanned downtime would prevent | +| | | broadcast of updates relating to new nodes connecting to / | +| | | disconnecting from the network, but not affect communication | +| | | between nodes whose connection state remains unchanged | +| | | throughout the incident. | ++------------+------------------------------------------------------------------------------+------------------------------------------------------------------+ + + + +### Elevation of Privilege + +Elevation of Privilege is enabling somebody to perform actions they are not permitted to do. Attacks range from a normal user executing actions as a more +privileged administrator, to a remote (external) attacker with no privileges executing arbitrary code. + ++------------+------------------------------------------------------------------------------+-----------------------------------------------------------------+ +| Element | Attack | Mitigations | ++============+==============================================================================+=================================================================+ +| Node | Malicious contract attempts to instantiate classes in the JVM that it is not | The AMQP serialiser uses a combination of white and black-lists | +| | authorised to access. | to mitigate against de-serialisation vulnerabilities. | +| | | | +| | Malicious CorDapp sends malformed serialised data to a peer. | Corda does not currently provide specific security controls to | +| | | mitigate all classes of privilege escalation vulnerabilities. | +| | **Impact** | The design of Corda requires that CorDapps are inherently | +| | | trusted by the node administrator. | +| | Unauthorised remote code execution would lead to complete system compromise. | | +| | | Future security research will introduce stronger controls that | +| | | can mitigate this class of threat. The Deterministic JVM will | +| | | provide a sandbox that prevents execution of code & classes | +| | | outside of the security boundary that contract code is | +| | | restricted to. | +| | | | +| | | | ++------------+------------------------------------------------------------------------------+-----------------------------------------------------------------+ +| RPC Client | A malicious RPC client connects to the node and impersonates another, | Nodes implement an access-control model that restricts what | +| | higher-privileged client on the same system, and initiates flows on their | actions RPC users can perform. | +| | behalf. | | +| | | Session replay is mitigated by virtue of the TLS protocol used | +| | | to protect RPC communications. | ++------------+------------------------------------------------------------------------------+-----------------------------------------------------------------+ + + + +Conclusion +---------- + +The threat model presented here describes the main threats to the Corda Network, and the controls that are included to mitigate these threats. It was necessary +to restrict this model to a high-level perspective of the Corda Network. It is hoped that enough information is provided to allow network participants to +understand the security model of Corda. + +Threat modelling is an on-going process. There is active research at R3 to continue evolving the Corda Threat Model. In particular, models are being developed +that focus more closely on individual components - such as the Node, Network Map and Doorman. + + + + diff --git a/docs/source/design/threat-model/images/threat-model.png b/docs/source/design/threat-model/images/threat-model.png new file mode 100644 index 0000000000000000000000000000000000000000..7735b61cf2aea1a684ff95d16bf8a508f6002ef7 GIT binary patch literal 23848 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 DyUat^ literal 0 HcmV?d00001 diff --git a/docs/source/index.rst b/docs/source/index.rst index 4f5119e2f8..fc92e187ef 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -80,6 +80,7 @@ We look forward to seeing what you can do with Corda! design/monitoring-management/design.md design/sgx-integration/design.md design/sgx-infrastructure/design.md + design/threat-model/corda-threat-model.md design/data-model-upgrades/signature-constraints.md .. conditional-toctree:: From 965f9ce528b95244acc35846f1268f095d5c7a92 Mon Sep 17 00:00:00 2001 From: Rick Parker Date: Mon, 24 Sep 2018 09:55:56 +0100 Subject: [PATCH 2/2] ENT-2431 Lay foundations for caching metrics (#3955) --- .../client/jfx/model/NetworkIdentityModel.kt | 3 +- .../net/corda/core/internal/NamedCache.kt | 6 --- .../messaging/ArtemisMessagingTest.kt | 8 ++- .../network/PersistentNetworkMapCacheTest.kt | 3 +- .../RaftTransactionCommitLogTests.kt | 3 +- .../net/corda/node/internal/AbstractNode.kt | 31 ++++++----- .../kotlin/net/corda/node/internal/Node.kt | 19 ++++--- .../node/services/api/ServiceHubInternal.kt | 2 + .../identity/PersistentIdentityService.kt | 17 +++--- .../keys/PersistentKeyManagementService.kt | 15 ++++-- .../messaging/P2PMessageDeduplicator.kt | 10 ++-- .../services/messaging/P2PMessagingClient.kt | 7 ++- .../network/PersistentNetworkMapCache.kt | 12 +++-- .../persistence/DBTransactionStorage.kt | 9 ++-- .../persistence/NodeAttachmentService.kt | 16 +++--- .../BFTNonValidatingNotaryService.kt | 3 +- .../PersistentUniquenessProvider.kt | 16 +++--- .../transactions/RaftUniquenessProvider.kt | 9 ++-- .../transactions/SimpleNotaryService.kt | 2 +- .../transactions/ValidatingNotaryService.kt | 2 +- .../node/utilities/AppendOnlyPersistentMap.kt | 13 +++-- .../corda/node/utilities/NodeNamedCache.kt | 54 +++++++++++++++++++ .../node/utilities/NonInvalidatingCache.kt | 21 ++++---- .../PersistentIdentityServiceTests.kt | 5 +- .../AppendOnlyPersistentMapTest.kt | 4 +- .../persistence/DBTransactionStorageTests.kt | 4 +- .../HibernateColumnConverterTests.kt | 3 +- .../persistence/NodeAttachmentServiceTest.kt | 3 +- .../PersistentUniquenessProviderTests.kt | 5 +- .../utilities/TestingNamedCacheFactory.kt | 33 ++++++++++++ .../corda/notarydemo/MyCustomNotaryService.kt | 2 +- .../node/internal/InternalMockNetwork.kt | 9 ++-- .../node/internal/performance/Reporter.kt | 8 +-- 33 files changed, 243 insertions(+), 114 deletions(-) create mode 100644 node/src/main/kotlin/net/corda/node/utilities/NodeNamedCache.kt create mode 100644 node/src/test/kotlin/net/corda/node/utilities/TestingNamedCacheFactory.kt diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt index 0b09957678..aa7f7e50f2 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt @@ -1,5 +1,6 @@ package net.corda.client.jfx.model +import com.github.benmanes.caffeine.cache.CacheLoader import com.github.benmanes.caffeine.cache.Caffeine import javafx.beans.value.ObservableValue import javafx.collections.FXCollections @@ -31,7 +32,7 @@ class NetworkIdentityModel { private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable) private val identityCache = Caffeine.newBuilder() - .buildNamed>("NetworkIdentityModel_identity", { publicKey -> + .buildNamed>("NetworkIdentityModel_identity", CacheLoader { publicKey: PublicKey -> publicKey.let { rpcProxy.map { it?.cordaRPCOps?.nodeInfoFromParty(AnonymousParty(publicKey)) } } }) val notaries = ChosenList(rpcProxy.map { FXCollections.observableList(it?.cordaRPCOps?.notaryIdentities() ?: emptyList()) }, "notaries") diff --git a/core/src/main/kotlin/net/corda/core/internal/NamedCache.kt b/core/src/main/kotlin/net/corda/core/internal/NamedCache.kt index 7b54b6ceb4..2a96de4835 100644 --- a/core/src/main/kotlin/net/corda/core/internal/NamedCache.kt +++ b/core/src/main/kotlin/net/corda/core/internal/NamedCache.kt @@ -29,12 +29,6 @@ fun Caffeine.buildNamed(name: String): Cache { return this.build() } -fun Caffeine.buildNamed(name: String, loadFunc: (K) -> V): LoadingCache { - checkCacheName(name) - return this.build(loadFunc) -} - - fun Caffeine.buildNamed(name: String, loader: CacheLoader): LoadingCache { checkCacheName(name) return this.build(loader) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt index 1ddef593f5..fef890d2ec 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt @@ -1,5 +1,6 @@ package net.corda.node.services.messaging +import com.codahale.metrics.MetricRegistry import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.crypto.generateKeyPair @@ -13,15 +14,16 @@ import net.corda.node.services.config.configureWithDevSSLCertificate import net.corda.node.services.network.PersistentNetworkMapCache import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor +import net.corda.node.utilities.TestingNamedCacheFactory import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.MAX_MESSAGE_SIZE import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.driver.PortAllocation -import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.internal.LogHelper import net.corda.testing.internal.rigorousMock +import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.internal.MOCK_VERSION_INFO import org.apache.activemq.artemis.api.core.ActiveMQConnectionTimedOutException @@ -88,7 +90,7 @@ class ArtemisMessagingTest { } LogHelper.setLevel(PersistentUniquenessProvider::class) database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null }) - networkMapCache = PersistentNetworkMapCache(database, rigorousMock()).apply { start(emptyList()) } + networkMapCache = PersistentNetworkMapCache(TestingNamedCacheFactory(), database, rigorousMock()).apply { start(emptyList()) } } @After @@ -223,6 +225,8 @@ class ArtemisMessagingTest { ServiceAffinityExecutor("ArtemisMessagingTests", 1), database, networkMapCache, + MetricRegistry(), + TestingNamedCacheFactory(), isDrainingModeOn = { false }, drainingModeWasChangedEvents = PublishSubject.create>()).apply { config.configureWithDevSSLCertificate() diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt index 6ac68ccf5b..1eea4f49db 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt @@ -5,6 +5,7 @@ import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.internal.configureDatabase import net.corda.node.internal.schemas.NodeInfoSchemaV1 import net.corda.node.services.identity.InMemoryIdentityService +import net.corda.node.utilities.TestingNamedCacheFactory import net.corda.nodeapi.internal.DEV_ROOT_CA import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.core.* @@ -27,7 +28,7 @@ class PersistentNetworkMapCacheTest { private var portCounter = 1000 private val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null }) - private val charlieNetMapCache = PersistentNetworkMapCache(database, InMemoryIdentityService(trustRoot = DEV_ROOT_CA.certificate)) + private val charlieNetMapCache = PersistentNetworkMapCache(TestingNamedCacheFactory(), database, InMemoryIdentityService(trustRoot = DEV_ROOT_CA.certificate)) @After fun cleanUp() { diff --git a/node/src/integration-test/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLogTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLogTests.kt index 3edb3d3f3e..38389ef595 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLogTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLogTests.kt @@ -16,6 +16,7 @@ import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.getOrThrow import net.corda.node.internal.configureDatabase import net.corda.node.services.schema.NodeSchemaService +import net.corda.node.utilities.TestingNamedCacheFactory import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.core.ALICE_NAME @@ -155,7 +156,7 @@ class RaftTransactionCommitLogTests { val address = Address(myAddress.host, myAddress.port) val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null }, NodeSchemaService(includeNotarySchemas = true)) databases.add(database) - val stateMachineFactory = { RaftTransactionCommitLog(database, Clock.systemUTC(), RaftUniquenessProvider.Companion::createMap) } + val stateMachineFactory = { RaftTransactionCommitLog(database, Clock.systemUTC(), { RaftUniquenessProvider.createMap(TestingNamedCacheFactory()) }) } val server = CopycatServer.builder(address) .withStateMachine(stateMachineFactory) diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 76392a3b51..2391cf7fca 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -64,10 +64,7 @@ import net.corda.node.services.statemachine.* import net.corda.node.services.transactions.* import net.corda.node.services.upgrade.ContractUpgradeServiceImpl import net.corda.node.services.vault.NodeVaultService -import net.corda.node.utilities.AffinityExecutor -import net.corda.node.utilities.JVMAgentRegistry -import net.corda.node.utilities.NamedThreadFactory -import net.corda.node.utilities.NodeBuildProperties +import net.corda.node.utilities.* import net.corda.nodeapi.internal.NodeInfoAndSigned import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.config.CertificateStore @@ -116,6 +113,7 @@ import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair // TODO Log warning if this node is a notary but not one of the ones specified in the network parameters, both for core and custom abstract class AbstractNode(val configuration: NodeConfiguration, val platformClock: CordaClock, + cacheFactoryPrototype: NamedCacheFactory, protected val versionInfo: VersionInfo, protected val cordappLoader: CordappLoader, protected val serverThread: AffinityExecutor.ServiceAffinityExecutor, @@ -125,6 +123,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration, @Suppress("LeakingThis") private var tokenizableServices: MutableList? = mutableListOf(platformClock, this) + + protected val metricRegistry = MetricRegistry() + protected val cacheFactory = cacheFactoryPrototype.bindWithConfig(configuration).bindWithMetrics(metricRegistry).tokenize() + val monitoringService = MonitoringService(metricRegistry).tokenize() + protected val runOnStop = ArrayList<() -> Any?>() init { @@ -139,7 +142,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } val schemaService = NodeSchemaService(cordappLoader.cordappSchemas, configuration.notary != null).tokenize() - val identityService = PersistentIdentityService().tokenize() + val identityService = PersistentIdentityService(cacheFactory).tokenize() val database: CordaPersistence = createCordaPersistence( configuration.database, identityService::wellKnownPartyFromX500Name, @@ -151,13 +154,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration, // TODO Break cyclic dependency identityService.database = database } - val networkMapCache = PersistentNetworkMapCache(database, identityService).tokenize() + + val networkMapCache = PersistentNetworkMapCache(cacheFactory, database, identityService).tokenize() val checkpointStorage = DBCheckpointStorage() @Suppress("LeakingThis") val transactionStorage = makeTransactionStorage(configuration.transactionCacheSizeBytes).tokenize() val networkMapClient: NetworkMapClient? = configuration.networkServices?.let { NetworkMapClient(it.networkMapURL, versionInfo) } - private val metricRegistry = MetricRegistry() - val attachments = NodeAttachmentService(metricRegistry, database, configuration.attachmentContentCacheSizeBytes, configuration.attachmentCacheBound).tokenize() + val attachments = NodeAttachmentService(metricRegistry, cacheFactory, database).tokenize() val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(), attachments).tokenize() @Suppress("LeakingThis") val keyManagementService = makeKeyManagementService(identityService).tokenize() @@ -166,7 +169,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val vaultService = makeVaultService(keyManagementService, servicesForResolution, database).tokenize() val nodeProperties = NodePropertiesPersistentStore(StubbedNodeUniqueIdProvider::value, database) val flowLogicRefFactory = FlowLogicRefFactoryImpl(cordappLoader.appClassLoader) - val monitoringService = MonitoringService(metricRegistry).tokenize() val networkMapUpdater = NetworkMapUpdater( networkMapCache, NodeInfoWatcher( @@ -314,7 +316,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, servicesForResolution.start(netParams) networkMapCache.start(netParams.notaries) - startDatabase(metricRegistry) + startDatabase() val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null) identityService.start(trustRoot, listOf(identity.certificate, nodeCa)) @@ -708,7 +710,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } protected open fun makeTransactionStorage(transactionCacheSizeBytes: Long): WritableTransactionStorage { - return DBTransactionStorage(transactionCacheSizeBytes, database) + return DBTransactionStorage(database, cacheFactory) } @VisibleForTesting @@ -768,7 +770,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, // Specific class so that MockNode can catch it. class DatabaseConfigurationException(message: String) : CordaException(message) - protected open fun startDatabase(metricRegistry: MetricRegistry? = null) { + protected open fun startDatabase() { val props = configuration.dataSourceProperties if (props.isEmpty) throw DatabaseConfigurationException("There must be a database configured.") database.startHikariPool(props, configuration.database, schemaService.internalSchemas(), metricRegistry) @@ -792,7 +794,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, // Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because // the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with // the identity key. But the infrastructure to make that easy isn't here yet. - return PersistentKeyManagementService(identityService, database) + return PersistentKeyManagementService(cacheFactory, identityService, database) } private fun makeCoreNotaryService(notaryConfig: NotaryConfig, myNotaryIdentity: PartyAndCertificate?): NotaryService { @@ -801,7 +803,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, return notaryConfig.run { when { raft != null -> { - val uniquenessProvider = RaftUniquenessProvider(configuration.baseDirectory, configuration.p2pSslOptions, database, platformClock, monitoringService.metrics, raft) + val uniquenessProvider = RaftUniquenessProvider(configuration.baseDirectory, configuration.p2pSslOptions, database, platformClock, monitoringService.metrics, cacheFactory, raft) (if (validating) ::RaftValidatingNotaryService else ::RaftNonValidatingNotaryService)(services, notaryKey, uniquenessProvider) } bftSMaRt != null -> { @@ -942,6 +944,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, override val clock: Clock get() = platformClock override val configuration: NodeConfiguration get() = this@AbstractNode.configuration override val networkMapUpdater: NetworkMapUpdater get() = this@AbstractNode.networkMapUpdater + override val cacheFactory: NamedCacheFactory get() = this@AbstractNode.cacheFactory private lateinit var _myInfo: NodeInfo override val myInfo: NodeInfo get() = _myInfo diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index 3d204ac7de..7b01e18c95 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -48,6 +48,7 @@ import net.corda.node.services.messaging.* import net.corda.node.services.rpc.ArtemisRpcBroker import net.corda.node.utilities.AddressUtils import net.corda.node.utilities.AffinityExecutor +import net.corda.node.utilities.DefaultNamedCacheFactory import net.corda.node.utilities.DemoClock import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_SHELL_USER import net.corda.nodeapi.internal.ShutdownHook @@ -93,6 +94,7 @@ open class Node(configuration: NodeConfiguration, ) : AbstractNode( configuration, createClock(configuration), + DefaultNamedCacheFactory(), versionInfo, cordappLoader, // Under normal (non-test execution) it will always be "1" @@ -195,7 +197,9 @@ open class Node(configuration: NodeConfiguration, database = database, networkMap = networkMapCache, isDrainingModeOn = nodeProperties.flowsDrainingMode::isEnabled, - drainingModeWasChangedEvents = nodeProperties.flowsDrainingMode.values + drainingModeWasChangedEvents = nodeProperties.flowsDrainingMode.values, + metricRegistry = metricRegistry, + cacheFactory = cacheFactory ) } @@ -332,7 +336,7 @@ open class Node(configuration: NodeConfiguration, * This is not using the H2 "automatic mixed mode" directly but leans on many of the underpinnings. For more details * on H2 URLs and configuration see: http://www.h2database.com/html/features.html#database_url */ - override fun startDatabase(metricRegistry: MetricRegistry?) { + override fun startDatabase() { val databaseUrl = configuration.dataSourceProperties.getProperty("dataSource.url") val h2Prefix = "jdbc:h2:file:" @@ -369,7 +373,7 @@ open class Node(configuration: NodeConfiguration, } } - super.startDatabase(metricRegistry) + super.startDatabase() database.closeOnStop() } @@ -418,12 +422,13 @@ open class Node(configuration: NodeConfiguration, // https://jolokia.org/agent/jvm.html JmxReporter.forRegistry(registry).inDomain("net.corda").createsObjectNamesWith { _, domain, name -> // Make the JMX hierarchy a bit better organised. - val category = name.substringBefore('.') + val category = name.substringBefore('.').substringBeforeLast('/') + val component = name.substringBefore('.').substringAfterLast('/', "") val subName = name.substringAfter('.', "") - if (subName == "") - ObjectName("$domain:name=$category") + (if (subName == "") + ObjectName("$domain:name=$category${if (component.isNotEmpty()) ",component=$component," else ""}") else - ObjectName("$domain:type=$category,name=$subName") + ObjectName("$domain:type=$category,${if (component.isNotEmpty()) "component=$component," else ""}name=$subName")) }.build().start() } diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index 674ba91b89..8d5583544f 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -25,6 +25,7 @@ import net.corda.node.services.network.NetworkMapUpdater import net.corda.node.services.persistence.AttachmentStorageInternal import net.corda.node.services.statemachine.ExternalEvent import net.corda.node.services.statemachine.FlowStateMachineImpl +import net.corda.node.utilities.NamedCacheFactory import net.corda.nodeapi.internal.persistence.CordaPersistence import java.security.PublicKey @@ -132,6 +133,7 @@ interface ServiceHubInternal : ServiceHub { } fun getFlowFactory(initiatingFlowClass: Class>): InitiatedFlowFactory<*>? + val cacheFactory: NamedCacheFactory } interface FlowStarter { diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index 8ed683b8bf..46f29b1ffd 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -10,6 +10,7 @@ import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug import net.corda.node.services.api.IdentityServiceInternal import net.corda.node.utilities.AppendOnlyPersistentMap +import net.corda.node.utilities.NamedCacheFactory import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.nodeapi.internal.persistence.CordaPersistence @@ -29,13 +30,14 @@ import javax.persistence.Lob * cached for efficient lookup. */ @ThreadSafe -class PersistentIdentityService : SingletonSerializeAsToken(), IdentityServiceInternal { +class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSerializeAsToken(), IdentityServiceInternal { companion object { private val log = contextLogger() - fun createPKMap(): AppendOnlyPersistentMap { + fun createPKMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap { return AppendOnlyPersistentMap( - "PersistentIdentityService_partyByKey", + cacheFactory = cacheFactory, + name = "PersistentIdentityService_partyByKey", toPersistentEntityKey = { it.toString() }, fromPersistentEntity = { Pair( @@ -50,9 +52,10 @@ class PersistentIdentityService : SingletonSerializeAsToken(), IdentityServiceIn ) } - fun createX500Map(): AppendOnlyPersistentMap { + fun createX500Map(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap { return AppendOnlyPersistentMap( - "PersistentIdentityService_partyByName", + cacheFactory = cacheFactory, + name = "PersistentIdentityService_partyByName", toPersistentEntityKey = { it.toString() }, fromPersistentEntity = { Pair(CordaX500Name.parse(it.name), SecureHash.parse(it.publicKeyHash)) }, toPersistentEntity = { key: CordaX500Name, value: SecureHash -> @@ -101,8 +104,8 @@ class PersistentIdentityService : SingletonSerializeAsToken(), IdentityServiceIn // CordaPersistence is not a c'tor parameter to work around the cyclic dependency lateinit var database: CordaPersistence - private val keyToParties = createPKMap() - private val principalToParties = createX500Map() + private val keyToParties = createPKMap(cacheFactory) + private val principalToParties = createX500Map(cacheFactory) fun start(trustRoot: X509Certificate, caCertificates: List = emptyList()) { _trustRoot = trustRoot diff --git a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt index ef6e2b3265..835a751c73 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt @@ -6,6 +6,7 @@ import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.MAX_HASH_HEX_SIZE import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.utilities.AppendOnlyPersistentMap +import net.corda.node.utilities.NamedCacheFactory import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY @@ -25,7 +26,7 @@ import javax.persistence.Lob * * This class needs database transactions to be in-flight during method calls and init. */ -class PersistentKeyManagementService(val identityService: PersistentIdentityService, +class PersistentKeyManagementService(cacheFactory: NamedCacheFactory, val identityService: PersistentIdentityService, private val database: CordaPersistence) : SingletonSerializeAsToken(), KeyManagementServiceInternal { @Entity @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}our_key_pairs") @@ -46,11 +47,15 @@ class PersistentKeyManagementService(val identityService: PersistentIdentityServ } private companion object { - fun createKeyMap(): AppendOnlyPersistentMap { + fun createKeyMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap { return AppendOnlyPersistentMap( - "PersistentKeyManagementService_keys", + cacheFactory = cacheFactory, + name = "PersistentKeyManagementService_keys", toPersistentEntityKey = { it.toStringShort() }, - fromPersistentEntity = { Pair(Crypto.decodePublicKey(it.publicKey), Crypto.decodePrivateKey(it.privateKey)) }, + fromPersistentEntity = { + Pair(Crypto.decodePublicKey(it.publicKey), + Crypto.decodePrivateKey(it.privateKey)) + }, toPersistentEntity = { key: PublicKey, value: PrivateKey -> PersistentKey(key, value) }, @@ -59,7 +64,7 @@ class PersistentKeyManagementService(val identityService: PersistentIdentityServ } } - private val keysMap = createKeyMap() + private val keysMap = createKeyMap(cacheFactory) override fun start(initialKeyPairs: Set) { initialKeyPairs.forEach { keysMap.addWithDuplicatesAllowed(it.public, it.private) } diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessageDeduplicator.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessageDeduplicator.kt index fcf89d2177..b8c29d8955 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessageDeduplicator.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessageDeduplicator.kt @@ -4,6 +4,7 @@ import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name import net.corda.node.services.statemachine.DeduplicationId import net.corda.node.utilities.AppendOnlyPersistentMap +import net.corda.node.utilities.NamedCacheFactory import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import java.time.Instant @@ -15,17 +16,18 @@ import javax.persistence.Id /** * Encapsulate the de-duplication logic. */ -class P2PMessageDeduplicator(private val database: CordaPersistence) { +class P2PMessageDeduplicator(cacheFactory: NamedCacheFactory, private val database: CordaPersistence) { // A temporary in-memory set of deduplication IDs and associated high water mark details. // When we receive a message we don't persist the ID immediately, // so we store the ID here in the meantime (until the persisting db tx has committed). This is because Artemis may // redeliver messages to the same consumer if they weren't ACKed. private val beingProcessedMessages = ConcurrentHashMap() - private val processedMessages = createProcessedMessages() + private val processedMessages = createProcessedMessages(cacheFactory) - private fun createProcessedMessages(): AppendOnlyPersistentMap { + private fun createProcessedMessages(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap { return AppendOnlyPersistentMap( - "P2PMessageDeduplicator_processedMessages", + cacheFactory = cacheFactory, + name = "P2PMessageDeduplicator_processedMessages", toPersistentEntityKey = { it.toString }, fromPersistentEntity = { Pair(DeduplicationId(it.id), MessageMeta(it.insertionTime, it.hash, it.seqNo)) }, toPersistentEntity = { key: DeduplicationId, value: MessageMeta -> diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt index 8d67ba191f..13b96c61b0 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt @@ -1,6 +1,7 @@ package net.corda.node.services.messaging import co.paralleluniverse.fibers.Suspendable +import com.codahale.metrics.MetricRegistry import net.corda.core.crypto.toStringShort import net.corda.core.identity.CordaX500Name import net.corda.core.internal.ThreadBox @@ -26,6 +27,7 @@ import net.corda.node.services.statemachine.DeduplicationId import net.corda.node.services.statemachine.ExternalEvent import net.corda.node.services.statemachine.SenderDeduplicationId import net.corda.node.utilities.AffinityExecutor +import net.corda.node.utilities.NamedCacheFactory import net.corda.nodeapi.internal.ArtemisMessagingComponent import net.corda.nodeapi.internal.ArtemisMessagingComponent.* import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_CONTROL @@ -81,6 +83,9 @@ class P2PMessagingClient(val config: NodeConfiguration, private val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor, private val database: CordaPersistence, private val networkMap: NetworkMapCacheInternal, + @Suppress("UNUSED") + private val metricRegistry: MetricRegistry, + cacheFactory: NamedCacheFactory, private val isDrainingModeOn: () -> Boolean, private val drainingModeWasChangedEvents: Observable> ) : SingletonSerializeAsToken(), MessagingService, AddressToArtemisQueueResolver { @@ -129,7 +134,7 @@ class P2PMessagingClient(val config: NodeConfiguration, private val handlers = ConcurrentHashMap() - private val deduplicator = P2PMessageDeduplicator(database) + private val deduplicator = P2PMessageDeduplicator(cacheFactory, database) internal var messagingExecutor: MessagingExecutor? = null /** diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index 4cacee3a59..f873120c32 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -23,6 +23,7 @@ import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug import net.corda.node.internal.schemas.NodeInfoSchemaV1 import net.corda.node.services.api.NetworkMapCacheInternal +import net.corda.node.utilities.NamedCacheFactory import net.corda.node.utilities.NonInvalidatingCache import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit @@ -36,7 +37,8 @@ import javax.annotation.concurrent.ThreadSafe /** Database-based network map cache. */ @ThreadSafe -open class PersistentNetworkMapCache(private val database: CordaPersistence, +open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory, + private val database: CordaPersistence, private val identityService: IdentityService) : NetworkMapCacheInternal, SingletonSerializeAsToken() { companion object { private val logger = contextLogger() @@ -124,8 +126,8 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, override fun getNodesByLegalIdentityKey(identityKey: PublicKey): List = nodesByKeyCache[identityKey]!! private val nodesByKeyCache = NonInvalidatingCache>( - "PersistentNetworkMap_nodesByKey", - 1024) { key -> + cacheFactory = cacheFactory, + name = "PersistentNetworkMap_nodesByKey") { key -> database.transaction { queryByIdentityKey(session, key) } } @@ -144,8 +146,8 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, } private val identityByLegalNameCache = NonInvalidatingCache>( - "PersistentNetworkMap_idByLegalName", - 1024) { name -> + cacheFactory = cacheFactory, + name = "PersistentNetworkMap_idByLegalName") { name -> Optional.ofNullable(database.transaction { queryIdentityByLegalName(session, name) }) } diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt index 1fe72c50a9..034e91a2cf 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt @@ -15,6 +15,7 @@ import net.corda.core.transactions.SignedTransaction import net.corda.node.services.api.WritableTransactionStorage import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.utilities.AppendOnlyPersistentMapBase +import net.corda.node.utilities.NamedCacheFactory import net.corda.node.utilities.WeightBasedAppendOnlyPersistentMap import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX @@ -31,7 +32,7 @@ typealias TxCacheValue = Pair, List { return WeightBasedAppendOnlyPersistentMap( + cacheFactory = cacheFactory, name = "DBTransactionStorage_transactions", toPersistentEntityKey = { it.toString() }, fromPersistentEntity = { @@ -67,7 +69,6 @@ class DBTransactionStorage(cacheSizeBytes: Long, private val database: CordaPers } }, persistentEntityClass = DBTransaction::class.java, - maxWeight = maxSizeInBytes, weighingFunc = { hash, tx -> hash.size + weighTx(tx) } ) } @@ -86,7 +87,7 @@ class DBTransactionStorage(cacheSizeBytes: Long, private val database: CordaPers } } - private val txStorage = ThreadBox(createTransactionsMap(cacheSizeBytes)) + private val txStorage = ThreadBox(createTransactionsMap(cacheFactory)) override fun addTransaction(transaction: SignedTransaction): Boolean = database.transaction { txStorage.locked { diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt index 78a62f2149..ca96ef1ad5 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt @@ -18,8 +18,8 @@ import net.corda.core.node.services.vault.AttachmentQueryCriteria import net.corda.core.node.services.vault.AttachmentSort import net.corda.core.serialization.* import net.corda.core.utilities.contextLogger -import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.vault.HibernateAttachmentQueryCriteriaParser +import net.corda.node.utilities.NamedCacheFactory import net.corda.node.utilities.NonInvalidatingCache import net.corda.node.utilities.NonInvalidatingWeightBasedCache import net.corda.nodeapi.exceptions.DuplicateAttachmentException @@ -43,9 +43,8 @@ import javax.persistence.* @ThreadSafe class NodeAttachmentService( metrics: MetricRegistry, - private val database: CordaPersistence, - attachmentContentCacheSize: Long = NodeConfiguration.defaultAttachmentContentCacheSize, - attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound + cacheFactory: NamedCacheFactory, + private val database: CordaPersistence ) : AttachmentStorageInternal, SingletonSerializeAsToken() { companion object { private val log = contextLogger() @@ -206,8 +205,8 @@ class NodeAttachmentService( // a problem somewhere else or this needs to be revisited. private val attachmentContentCache = NonInvalidatingWeightBasedCache( + cacheFactory = cacheFactory, name = "NodeAttachmentService_attachmentContent", - maxWeight = attachmentContentCacheSize, weigher = Weigher>> { key, value -> key.size + if (value.isPresent) value.get().second.size else 0 }, loadFunction = { Optional.ofNullable(loadAttachmentContent(it)) } ) @@ -228,10 +227,9 @@ class NodeAttachmentService( } private val attachmentCache = NonInvalidatingCache>( - "NodeAttachmentService_attachemnt", - attachmentCacheBound) { key -> - Optional.ofNullable(createAttachment(key)) - } + cacheFactory = cacheFactory, + name = "NodeAttachmentService_attachmentPresence", + loadFunction = { key -> Optional.ofNullable(createAttachment(key)) }) private fun createAttachment(key: SecureHash): Attachment? { val content = attachmentContentCache.get(key)!! diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt index d6963e7130..156640c443 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt @@ -102,7 +102,8 @@ class BFTNonValidatingNotaryService( private fun createMap(): AppendOnlyPersistentMap { return AppendOnlyPersistentMap( - "BFTNonValidatingNotaryService_transactions", + cacheFactory = services.cacheFactory, + name = "BFTNonValidatingNotaryService_transactions", toPersistentEntityKey = { PersistentStateRef(it.txhash.toString(), it.index) }, fromPersistentEntity = { //TODO null check will become obsolete after making DB/JPA columns not nullable diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt index eadedd4e64..b6c8ebf437 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt @@ -11,7 +11,10 @@ import net.corda.core.flows.StateConsumptionDetails import net.corda.core.identity.Party import net.corda.core.internal.concurrent.OpenFuture import net.corda.core.internal.concurrent.openFuture -import net.corda.core.internal.notary.* +import net.corda.core.internal.notary.AsyncUniquenessProvider +import net.corda.core.internal.notary.NotaryInternalException +import net.corda.core.internal.notary.isConsumedByTheSameTx +import net.corda.core.internal.notary.validateTimeWindow import net.corda.core.schemas.PersistentStateRef import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SingletonSerializeAsToken @@ -19,10 +22,10 @@ import net.corda.core.serialization.serialize import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug import net.corda.node.utilities.AppendOnlyPersistentMap +import net.corda.node.utilities.NamedCacheFactory import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import net.corda.nodeapi.internal.persistence.currentDBSession -import net.corda.serialization.internal.CordaSerializationEncoding import java.time.Clock import java.time.Instant import java.util.* @@ -33,7 +36,7 @@ import kotlin.concurrent.thread /** A RDBMS backed Uniqueness provider */ @ThreadSafe -class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersistence) : AsyncUniquenessProvider, SingletonSerializeAsToken() { +class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersistence, cacheFactory: NamedCacheFactory) : AsyncUniquenessProvider, SingletonSerializeAsToken() { @MappedSuperclass class BaseComittedState( @@ -80,7 +83,7 @@ class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersiste @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}notary_committed_states") class CommittedState(id: PersistentStateRef, consumingTxHash: String) : BaseComittedState(id, consumingTxHash) - private val commitLog = createMap() + private val commitLog = createMap(cacheFactory) private val requestQueue = LinkedBlockingQueue(requestQueueSize) @@ -98,9 +101,10 @@ class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersiste companion object { private const val requestQueueSize = 100_000 private val log = contextLogger() - fun createMap(): AppendOnlyPersistentMap = + fun createMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap = AppendOnlyPersistentMap( - "PersistentUniquenessProvider_transactions", + cacheFactory = cacheFactory, + name = "PersistentUniquenessProvider_transactions", toPersistentEntityKey = { PersistentStateRef(it.txhash.toString(), it.index) }, fromPersistentEntity = { //TODO null check will become obsolete after making DB/JPA columns not nullable diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt index 1265461d17..2192343fa1 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt @@ -28,6 +28,7 @@ import net.corda.core.utilities.debug import net.corda.node.services.config.RaftConfig import net.corda.node.services.transactions.RaftTransactionCommitLog.Commands.CommitTransaction import net.corda.node.utilities.AppendOnlyPersistentMap +import net.corda.node.utilities.NamedCacheFactory import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX @@ -55,13 +56,15 @@ class RaftUniquenessProvider( private val db: CordaPersistence, private val clock: Clock, private val metrics: MetricRegistry, + private val cacheFactory: NamedCacheFactory, private val raftConfig: RaftConfig ) : UniquenessProvider, SingletonSerializeAsToken() { companion object { private val log = contextLogger() - fun createMap(): AppendOnlyPersistentMap, CommittedState, PersistentStateRef> = + fun createMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap, CommittedState, PersistentStateRef> = AppendOnlyPersistentMap( - "RaftUniquenessProvider_transactions", + cacheFactory = cacheFactory, + name = "RaftUniquenessProvider_transactions", toPersistentEntityKey = { PersistentStateRef(it) }, fromPersistentEntity = { val txId = it.id.txId @@ -109,7 +112,7 @@ class RaftUniquenessProvider( fun start() { log.info("Creating Copycat server, log stored in: ${storagePath.toAbsolutePath()}") val stateMachineFactory = { - RaftTransactionCommitLog(db, clock, RaftUniquenessProvider.Companion::createMap) + RaftTransactionCommitLog(db, clock, { createMap(cacheFactory) }) } val address = raftConfig.nodeAddress.let { Address(it.host, it.port) } val storage = buildStorage(storagePath) diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt index 16ad4464af..51909ea5b1 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt @@ -8,7 +8,7 @@ import java.security.PublicKey /** A simple Notary service that does not perform transaction validation */ class SimpleNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() { - override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database) + override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database, services.cacheFactory) override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow = NonValidatingNotaryFlow(otherPartySession, this) diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt index 4a6f46b2ce..6e39a3ea1e 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt @@ -8,7 +8,7 @@ import java.security.PublicKey /** A Notary service that validates the transaction chain of the submitted transaction before committing it */ class ValidatingNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() { - override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database) + override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database, services.cacheFactory) override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow = ValidatingNotaryFlow(otherPartySession, this) diff --git a/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt b/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt index 274ceb6c5a..81f080e425 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt @@ -309,21 +309,20 @@ abstract class AppendOnlyPersistentMapBase( // Open for tests to override open class AppendOnlyPersistentMap( + cacheFactory: NamedCacheFactory, name: String, toPersistentEntityKey: (K) -> EK, fromPersistentEntity: (E) -> Pair, toPersistentEntity: (key: K, value: V) -> E, - persistentEntityClass: Class, - cacheBound: Long = 1024 + persistentEntityClass: Class ) : AppendOnlyPersistentMapBase( toPersistentEntityKey, fromPersistentEntity, toPersistentEntity, persistentEntityClass) { - //TODO determine cacheBound based on entity class later or with node config allowing tuning, or using some heuristic based on heap size override val cache = NonInvalidatingCache( + cacheFactory = cacheFactory, name = name, - bound = cacheBound, loadFunction = { key: K -> // This gets called if a value is read and the cache has no Transactional for this key yet. val value: V? = loadValue(key) @@ -355,12 +354,12 @@ open class AppendOnlyPersistentMap( // Same as above, but with weighted values (e.g. memory footprint sensitive). class WeightBasedAppendOnlyPersistentMap( + cacheFactory: NamedCacheFactory, name: String, toPersistentEntityKey: (K) -> EK, fromPersistentEntity: (E) -> Pair, toPersistentEntity: (key: K, value: V) -> E, persistentEntityClass: Class, - maxWeight: Long, weighingFunc: (K, Transactional) -> Int ) : AppendOnlyPersistentMapBase( toPersistentEntityKey, @@ -368,8 +367,8 @@ class WeightBasedAppendOnlyPersistentMap( toPersistentEntity, persistentEntityClass) { override val cache = NonInvalidatingWeightBasedCache( - name, - maxWeight = maxWeight, + cacheFactory = cacheFactory, + name = name, weigher = Weigher { key, value -> weighingFunc(key, value) }, loadFunction = { key: K -> val value: V? = loadValue(key) diff --git a/node/src/main/kotlin/net/corda/node/utilities/NodeNamedCache.kt b/node/src/main/kotlin/net/corda/node/utilities/NodeNamedCache.kt new file mode 100644 index 0000000000..d11c9667c2 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/utilities/NodeNamedCache.kt @@ -0,0 +1,54 @@ +package net.corda.node.utilities + +import com.codahale.metrics.MetricRegistry +import com.github.benmanes.caffeine.cache.Cache +import com.github.benmanes.caffeine.cache.CacheLoader +import com.github.benmanes.caffeine.cache.Caffeine +import com.github.benmanes.caffeine.cache.LoadingCache +import net.corda.core.internal.buildNamed +import net.corda.core.serialization.SerializeAsToken +import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.node.services.config.NodeConfiguration + +/** + * Allow passing metrics and config to caching implementations. + */ +interface NamedCacheFactory : SerializeAsToken { + /** + * Build a new cache factory of the same type that incorporates metrics. + */ + fun bindWithMetrics(metricRegistry: MetricRegistry): NamedCacheFactory + + /** + * Build a new cache factory of the same type that incorporates the associated configuration. + */ + fun bindWithConfig(nodeConfiguration: NodeConfiguration): NamedCacheFactory + + fun buildNamed(caffeine: Caffeine, name: String): Cache + fun buildNamed(caffeine: Caffeine, name: String, loader: CacheLoader): LoadingCache +} + +class DefaultNamedCacheFactory private constructor(private val metricRegistry: MetricRegistry?, private val nodeConfiguration: NodeConfiguration?) : NamedCacheFactory, SingletonSerializeAsToken() { + constructor() : this(null, null) + + override fun bindWithMetrics(metricRegistry: MetricRegistry): NamedCacheFactory = DefaultNamedCacheFactory(metricRegistry, this.nodeConfiguration) + override fun bindWithConfig(nodeConfiguration: NodeConfiguration): NamedCacheFactory = DefaultNamedCacheFactory(this.metricRegistry, nodeConfiguration) + + override fun buildNamed(caffeine: Caffeine, name: String): Cache { + checkNotNull(metricRegistry) + checkNotNull(nodeConfiguration) + return caffeine.maximumSize(1024).buildNamed(name) + } + + override fun buildNamed(caffeine: Caffeine, name: String, loader: CacheLoader): LoadingCache { + checkNotNull(metricRegistry) + checkNotNull(nodeConfiguration) + val configuredCaffeine = when (name) { + "DBTransactionStorage_transactions" -> caffeine.maximumWeight(nodeConfiguration!!.transactionCacheSizeBytes) + "NodeAttachmentService_attachmentContent" -> caffeine.maximumWeight(nodeConfiguration!!.attachmentContentCacheSizeBytes) + "NodeAttachmentService_attachmentPresence" -> caffeine.maximumSize(nodeConfiguration!!.attachmentCacheBound) + else -> caffeine.maximumSize(1024) + } + return configuredCaffeine.buildNamed(name, loader) + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt b/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt index 814ef0102e..2cf4904282 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt @@ -4,19 +4,18 @@ import com.github.benmanes.caffeine.cache.CacheLoader import com.github.benmanes.caffeine.cache.Caffeine import com.github.benmanes.caffeine.cache.LoadingCache import com.github.benmanes.caffeine.cache.Weigher -import net.corda.core.internal.buildNamed class NonInvalidatingCache private constructor( val cache: LoadingCache ) : LoadingCache by cache { - constructor(name: String, bound: Long, loadFunction: (K) -> V) : - this(buildCache(name, bound, loadFunction)) + constructor(cacheFactory: NamedCacheFactory, name: String, loadFunction: (K) -> V) : + this(buildCache(cacheFactory, name, loadFunction)) private companion object { - private fun buildCache(name: String, bound: Long, loadFunction: (K) -> V): LoadingCache { - val builder = Caffeine.newBuilder().maximumSize(bound) - return builder.buildNamed(name, NonInvalidatingCacheLoader(loadFunction)) + private fun buildCache(cacheFactory: NamedCacheFactory, name: String, loadFunction: (K) -> V): LoadingCache { + val builder = Caffeine.newBuilder() + return cacheFactory.buildNamed(builder, name, NonInvalidatingCacheLoader(loadFunction)) } } @@ -33,13 +32,13 @@ class NonInvalidatingCache private constructor( class NonInvalidatingWeightBasedCache private constructor( val cache: LoadingCache ) : LoadingCache by cache { - constructor (name: String, maxWeight: Long, weigher: Weigher, loadFunction: (K) -> V) : - this(buildCache(name, maxWeight, weigher, loadFunction)) + constructor (cacheFactory: NamedCacheFactory, name: String, weigher: Weigher, loadFunction: (K) -> V) : + this(buildCache(cacheFactory, name, weigher, loadFunction)) private companion object { - private fun buildCache(name: String, maxWeight: Long, weigher: Weigher, loadFunction: (K) -> V): LoadingCache { - val builder = Caffeine.newBuilder().maximumWeight(maxWeight).weigher(weigher) - return builder.buildNamed(name, NonInvalidatingCache.NonInvalidatingCacheLoader(loadFunction)) + private fun buildCache(cacheFactory: NamedCacheFactory, name: String, weigher: Weigher, loadFunction: (K) -> V): LoadingCache { + val builder = Caffeine.newBuilder().weigher(weigher) + return cacheFactory.buildNamed(builder, name, NonInvalidatingCache.NonInvalidatingCacheLoader(loadFunction)) } } } \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt index 927608421c..091a47d168 100644 --- a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt @@ -8,6 +8,7 @@ import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.node.internal.configureDatabase +import net.corda.node.utilities.TestingNamedCacheFactory import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.x509Certificates @@ -46,7 +47,7 @@ class PersistentIdentityServiceTests { @Before fun setup() { - identityService = PersistentIdentityService() + identityService = PersistentIdentityService(TestingNamedCacheFactory()) database = configureDatabase( makeTestDataSourceProperties(), DatabaseConfig(), @@ -218,7 +219,7 @@ class PersistentIdentityServiceTests { identityService.verifyAndRegisterIdentity(anonymousBob) // Create new identity service mounted onto same DB - val newPersistentIdentityService = PersistentIdentityService().also { + val newPersistentIdentityService = PersistentIdentityService(TestingNamedCacheFactory()).also { it.database = database it.start(DEV_ROOT_CA.certificate) } diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt index 69646a6ccc..5ed16e74a8 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt @@ -5,6 +5,7 @@ import net.corda.core.utilities.loggerFor import net.corda.node.internal.configureDatabase import net.corda.node.services.schema.NodeSchemaService import net.corda.node.utilities.AppendOnlyPersistentMap +import net.corda.node.utilities.TestingNamedCacheFactory import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import org.junit.After @@ -271,7 +272,8 @@ class AppendOnlyPersistentMapTest(var scenario: Scenario) { ) class TestMap : AppendOnlyPersistentMap( - "ApoendOnlyPersistentMap_test", + cacheFactory = TestingNamedCacheFactory(), + name = "ApoendOnlyPersistentMap_test", toPersistentEntityKey = { it }, fromPersistentEntity = { Pair(it.key, it.value) }, toPersistentEntity = { key: Long, value: String -> diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt index 44703546e2..f0c7c95859 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt @@ -8,8 +8,8 @@ import net.corda.core.crypto.TransactionSignature import net.corda.core.toFuture import net.corda.core.transactions.SignedTransaction import net.corda.node.internal.configureDatabase -import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.transactions.PersistentUniquenessProvider +import net.corda.node.utilities.TestingNamedCacheFactory import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.core.* @@ -154,7 +154,7 @@ class DBTransactionStorageTests { } private fun newTransactionStorage(cacheSizeBytesOverride: Long? = null) { - transactionStorage = DBTransactionStorage(cacheSizeBytesOverride ?: NodeConfiguration.defaultTransactionCacheSize, database) + transactionStorage = DBTransactionStorage(database, TestingNamedCacheFactory(cacheSizeBytesOverride ?: 1024)) } private fun assertTransactionIsRetrievable(transaction: SignedTransaction) { diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateColumnConverterTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateColumnConverterTests.kt index 4832474cac..0c5213eb63 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateColumnConverterTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateColumnConverterTests.kt @@ -9,6 +9,7 @@ import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.CashIssueFlow import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.keys.E2ETestKeyManagementService +import net.corda.node.utilities.TestingNamedCacheFactory import net.corda.testing.core.BOC_NAME import net.corda.testing.node.InMemoryMessagingNetwork import net.corda.testing.node.MockNetwork @@ -47,7 +48,7 @@ class HibernateColumnConverterTests { val ref = OpaqueBytes.of(0x01) // Create parallel set of key and identity services so that the values are not cached, forcing the node caches to do a lookup. - val identityService = PersistentIdentityService() + val identityService = PersistentIdentityService(TestingNamedCacheFactory()) val originalIdentityService: PersistentIdentityService = bankOfCordaNode.services.identityService as PersistentIdentityService identityService.database = originalIdentityService.database identityService.start(originalIdentityService.trustRoot) diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt index baee00a8f3..70c827b496 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt @@ -15,6 +15,7 @@ import net.corda.core.node.services.vault.Sort import net.corda.core.utilities.getOrThrow import net.corda.node.internal.configureDatabase import net.corda.node.services.transactions.PersistentUniquenessProvider +import net.corda.node.utilities.TestingNamedCacheFactory import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.internal.LogHelper @@ -51,7 +52,7 @@ class NodeAttachmentServiceTest { val dataSourceProperties = makeTestDataSourceProperties() database = configureDatabase(dataSourceProperties, DatabaseConfig(), { null }, { null }) fs = Jimfs.newFileSystem(Configuration.unix()) - storage = NodeAttachmentService(MetricRegistry(), database).also { + storage = NodeAttachmentService(MetricRegistry(), TestingNamedCacheFactory(), database).also { database.transaction { it.start() } diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt index bdc2b642d0..4bf28c246c 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt @@ -10,6 +10,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.internal.notary.NotaryInternalException import net.corda.node.internal.configureDatabase import net.corda.node.services.schema.NodeSchemaService +import net.corda.node.utilities.TestingNamedCacheFactory import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.core.SerializationEnvironmentRule @@ -49,7 +50,7 @@ class PersistentUniquenessProviderTests { @Test fun `should commit a transaction with unused inputs without exception`() { - val provider = PersistentUniquenessProvider(Clock.systemUTC(), database) + val provider = PersistentUniquenessProvider(Clock.systemUTC(), database, TestingNamedCacheFactory()) val inputState = generateStateRef() provider.commit(listOf(inputState), txID, identity, requestSignature) @@ -57,7 +58,7 @@ class PersistentUniquenessProviderTests { @Test fun `should report a conflict for a transaction with previously used inputs`() { - val provider = PersistentUniquenessProvider(Clock.systemUTC(), database) + val provider = PersistentUniquenessProvider(Clock.systemUTC(), database, TestingNamedCacheFactory()) val inputState = generateStateRef() val inputs = listOf(inputState) diff --git a/node/src/test/kotlin/net/corda/node/utilities/TestingNamedCacheFactory.kt b/node/src/test/kotlin/net/corda/node/utilities/TestingNamedCacheFactory.kt new file mode 100644 index 0000000000..4582246e09 --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/utilities/TestingNamedCacheFactory.kt @@ -0,0 +1,33 @@ +package net.corda.node.utilities + +import com.codahale.metrics.MetricRegistry +import com.github.benmanes.caffeine.cache.Cache +import com.github.benmanes.caffeine.cache.CacheLoader +import com.github.benmanes.caffeine.cache.Caffeine +import com.github.benmanes.caffeine.cache.LoadingCache +import net.corda.core.internal.buildNamed +import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.node.services.config.MB +import net.corda.node.services.config.NodeConfiguration + +class TestingNamedCacheFactory private constructor(private val sizeOverride: Long, private val metricRegistry: MetricRegistry?, private val nodeConfiguration: NodeConfiguration?) : NamedCacheFactory, SingletonSerializeAsToken() { + constructor(sizeOverride: Long = 1024) : this(sizeOverride, null, null) + + override fun bindWithMetrics(metricRegistry: MetricRegistry): NamedCacheFactory = TestingNamedCacheFactory(sizeOverride, metricRegistry, this.nodeConfiguration) + override fun bindWithConfig(nodeConfiguration: NodeConfiguration): NamedCacheFactory = TestingNamedCacheFactory(sizeOverride, this.metricRegistry, nodeConfiguration) + + override fun buildNamed(caffeine: Caffeine, name: String): Cache { + // Does not check metricRegistry or nodeConfiguration, because for tests we don't care. + return caffeine.maximumSize(sizeOverride).buildNamed(name) + } + + override fun buildNamed(caffeine: Caffeine, name: String, loader: CacheLoader): LoadingCache { + // Does not check metricRegistry or nodeConfiguration, because for tests we don't care. + val configuredCaffeine = when (name) { + "DBTransactionStorage_transactions" -> caffeine.maximumWeight(1.MB) + "NodeAttachmentService_attachmentContent" -> caffeine.maximumWeight(1.MB) + else -> caffeine.maximumSize(sizeOverride) + } + return configuredCaffeine.buildNamed(name, loader) + } +} \ No newline at end of file diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt index 666844691c..ad684fc489 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt @@ -27,7 +27,7 @@ import java.security.SignatureException // START 1 @CordaService class MyCustomValidatingNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() { - override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database) + override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database, services.cacheFactory) override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic = MyValidatingNotaryFlow(otherPartySession, this) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt index ae0a4d00f4..ba52e79e72 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt @@ -1,6 +1,5 @@ package net.corda.testing.node.internal -import com.codahale.metrics.MetricRegistry import com.google.common.jimfs.Configuration.unix import com.google.common.jimfs.Jimfs import com.nhaarman.mockito_kotlin.doReturn @@ -47,6 +46,7 @@ import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.services.transactions.BFTNonValidatingNotaryService import net.corda.node.services.transactions.BFTSMaRt import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor +import net.corda.node.utilities.DefaultNamedCacheFactory import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.network.NetworkParametersCopier @@ -54,9 +54,9 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.driver.TestCorDapp -import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.setGlobalSerialization +import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.internal.testThreadFactory import net.corda.testing.node.* import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties @@ -279,6 +279,7 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe open class MockNode(args: MockNodeArgs, cordappLoader: CordappLoader = JarScanningCordappLoader.fromDirectories(args.config.cordappDirectories)) : AbstractNode( args.config, TestClock(Clock.systemUTC()), + DefaultNamedCacheFactory(), args.version, cordappLoader, args.network.getServerThread(args.id), @@ -405,8 +406,8 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe get() = _serializationWhitelists private var dbCloser: (() -> Any?)? = null - override fun startDatabase(metricRegistry: MetricRegistry?) { - super.startDatabase(metricRegistry) + override fun startDatabase() { + super.startDatabase() dbCloser = database::close runOnStop += dbCloser!! } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/performance/Reporter.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/performance/Reporter.kt index a0a9e48a0f..cbd6cd5a00 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/performance/Reporter.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/performance/Reporter.kt @@ -12,12 +12,14 @@ fun startReporter(shutdownManager: ShutdownManager, metricRegistry: MetricRegist val jmxReporter = thread { JmxReporter.forRegistry(metricRegistry).inDomain("net.corda").createsObjectNamesWith { _, domain, name -> // Make the JMX hierarchy a bit better organised. - val category = name.substringBefore('.') + val category = name.substringBefore('.').substringBeforeLast('/') + val component = name.substringBefore('.').substringAfterLast('/', "") val subName = name.substringAfter('.', "") if (subName == "") - ObjectName("$domain:name=$category") + ObjectName("$domain:name=$category${if (component.isNotEmpty()) ",component=$component," else ""}") else - ObjectName("$domain:type=$category,name=$subName") + ObjectName("$domain:type=$category,${if (component.isNotEmpty()) "component=$component," else ""}name=$subName") + }.build().start() } shutdownManager.registerShutdown { jmxReporter.interrupt() }