|
| 1 | +Pluggable transaction endorsement and validation |
| 2 | +================================================ |
| 3 | + |
| 4 | +Motivation |
| 5 | +---------- |
| 6 | + |
| 7 | +When a transaction is validated at time of commit, the peer performs various |
| 8 | +checks before applying the state changes that come with the transaction itself: |
| 9 | + |
| 10 | +- Validating the identities that signed the transaction. |
| 11 | +- Verifying the signatures of the endorsers on the transaction. |
| 12 | +- Ensuring the transaction satisfies the endorsement policies of the namespaces |
| 13 | + of the corresponding chaincodes. |
| 14 | + |
| 15 | +There are use cases which demand custom transaction validation rules different |
| 16 | +from the default Fabric validation rules, such as: |
| 17 | + |
| 18 | +- **State-based endorsement:** When the endorsement policy depends on the key, |
| 19 | + and not only on the namespace. |
| 20 | +- **UTXO (Unspent Transaction Output):** When the validation takes into account |
| 21 | + whether the transaction doesn't double spend its inputs. |
| 22 | +- **Anonymous transactions:** When the endorsement doesn't contain the identity |
| 23 | + of the peer, but a signature and a public key are shared that can't be linked |
| 24 | + to the peer's identity. |
| 25 | + |
| 26 | +Pluggable endorsement and validation logic |
| 27 | +------------------------------------------ |
| 28 | + |
| 29 | +Fabric allows for the implementation and deployment of custom endorsement and |
| 30 | +validation logic into the peer to be associated with chaincode handling in a |
| 31 | +pluggable manner. This logic can be either compiled into the peer as built in |
| 32 | +selectable logic, or compiled and deployed alongside the peer as a |
| 33 | +`Golang plugin <https://golang.org/pkg/plugin/>`_. |
| 34 | + |
| 35 | +Recall that every chaincode is associated with its own endorsement and validation |
| 36 | +logic at the time of chaincode instantiation. If the user doesn't select one, the |
| 37 | +default built-in logic is selected implicitly. A peer administrator may alter the |
| 38 | +endorsement/validation logic that is selected by extending the peer's local |
| 39 | +configuration with the customization of the endorsement/validation logic which is |
| 40 | +loaded and applied at peer startup. |
| 41 | + |
| 42 | +Configuration |
| 43 | +------------- |
| 44 | + |
| 45 | +Each peer has a local configuration (``core.yaml``) that declares a mapping |
| 46 | +between the endorsement/validation logic name and the implementation that is to |
| 47 | +be run. |
| 48 | + |
| 49 | +The default logic are called ``ESCC`` (with the "E" standing for endorsement) and |
| 50 | +``VSCC`` (validation), and they can be found in the peer local configuration in |
| 51 | +the ``handlers`` section: |
| 52 | + |
| 53 | +.. code-block:: YAML |
| 54 | +
|
| 55 | + handlers: |
| 56 | + endorsers: |
| 57 | + escc: |
| 58 | + name: DefaultEndorsement |
| 59 | + validators: |
| 60 | + vscc: |
| 61 | + name: DefaultValidation |
| 62 | +
|
| 63 | +When the endorsement or validation implementation is compiled into the peer, the |
| 64 | +``name`` property represents the initialization function that is to be run in order |
| 65 | +to obtain the factory that creates instances of the endorsement/validation logic. |
| 66 | + |
| 67 | +The function is an instance method of the ``HandlerLibrary`` construct under |
| 68 | +``core/handlers/library/library.go`` and in order for custom endorsement or |
| 69 | +validation logic to be added, this construct needs to be extended with any |
| 70 | +additional methods. |
| 71 | + |
| 72 | +Since this is cumbersome and poses a deployment challenge, one can also deploy |
| 73 | +custom endorsement and validation as a Golang plugin by adding another property |
| 74 | +under the ``name`` called ``library``. |
| 75 | + |
| 76 | +For example, if we have custom endorsement and validation logic that represents |
| 77 | +state-based endorsement which is implemented as a plugin, we would have the following |
| 78 | +entries in the configuration in ``core.yaml``: |
| 79 | + |
| 80 | +.. code-block:: YAML |
| 81 | +
|
| 82 | + handlers: |
| 83 | + endorsers: |
| 84 | + escc: |
| 85 | + name: DefaultEndorsement |
| 86 | + statebased: |
| 87 | + name: state_based |
| 88 | + library: /etc/hyperledger/fabric/plugins/state_based_endorsement.so |
| 89 | + validators: |
| 90 | + vscc: |
| 91 | + name: DefaultValidation |
| 92 | + statebased: |
| 93 | + name: state_based |
| 94 | + library: /etc/hyperledger/fabric/plugins/state_based_validation.so |
| 95 | +
|
| 96 | +And we'd have to place the ``.so`` plugin files in the peer's local file system. |
| 97 | + |
| 98 | +.. note:: Hereafter, custom endorsement or validation logic implementation is |
| 99 | + going to be referred to as "plugins", even if they are compiled into |
| 100 | + the peer. |
| 101 | + |
| 102 | +Endorsement plugin implementation |
| 103 | +--------------------------------- |
| 104 | + |
| 105 | +To implement an endorsement plugin, one must implement the ``Plugin`` interface |
| 106 | +found in ``core/handlers/endorsement/api/endorsement.go``: |
| 107 | + |
| 108 | +.. code-block:: Go |
| 109 | +
|
| 110 | + // Plugin endorses a proposal response |
| 111 | + type Plugin interface { |
| 112 | + // Endorse signs the given payload(ProposalResponsePayload bytes), and optionally mutates it. |
| 113 | + // Returns: |
| 114 | + // The Endorsement: A signature over the payload, and an identity that is used to verify the signature |
| 115 | + // The payload that was given as input (could be modified within this function) |
| 116 | + // Or error on failure |
| 117 | + Endorse(payload []byte, sp *peer.SignedProposal) (*peer.Endorsement, []byte, error) |
| 118 | +
|
| 119 | + // Init injects dependencies into the instance of the Plugin |
| 120 | + Init(dependencies ...Dependency) error |
| 121 | + } |
| 122 | +
|
| 123 | +An endorsement plugin instance of a given plugin type (identified either by the |
| 124 | +method name as an instance method of the ``HandlerLibrary`` or by the plugin ``.so`` |
| 125 | +file path) is created for each channel by having the peer invoke the ``New`` |
| 126 | +method in the ``PluginFactory`` interface which is also expected to be implemented |
| 127 | +by the plugin developer: |
| 128 | + |
| 129 | +.. code-block:: Go |
| 130 | +
|
| 131 | + // PluginFactory creates a new instance of a Plugin |
| 132 | + type PluginFactory interface { |
| 133 | + New() Plugin |
| 134 | + } |
| 135 | +
|
| 136 | +
|
| 137 | +The ``Init`` method is expected to receive as input all the dependencies declared |
| 138 | +under ``core/handlers/endorsement/api/``, identified as embedding the ``Dependency`` |
| 139 | +interface. |
| 140 | + |
| 141 | +After the creation of the ``Plugin`` instance, the ``Init`` method is invoked on |
| 142 | +it by the peer with the ``dependencies`` passed as parameters. |
| 143 | + |
| 144 | +Currently Fabric comes with the following dependencies for endorsement plugins: |
| 145 | + |
| 146 | +- ``SigningIdentityFetcher``: Returns an instance of ``SigningIdentity`` based |
| 147 | + on a given signed proposal: |
| 148 | + |
| 149 | +.. code-block:: Go |
| 150 | +
|
| 151 | + // SigningIdentity signs messages and serializes its public identity to bytes |
| 152 | + type SigningIdentity interface { |
| 153 | + // Serialize returns a byte representation of this identity which is used to verify |
| 154 | + // messages signed by this SigningIdentity |
| 155 | + Serialize() ([]byte, error) |
| 156 | +
|
| 157 | + // Sign signs the given payload and returns a signature |
| 158 | + Sign([]byte) ([]byte, error) |
| 159 | + } |
| 160 | +
|
| 161 | +- ``StateFetcher``: Fetches a **State** object which interacts with the world |
| 162 | + state: |
| 163 | + |
| 164 | +.. code-block:: Go |
| 165 | +
|
| 166 | + // State defines interaction with the world state |
| 167 | + type State interface { |
| 168 | + // GetPrivateDataMultipleKeys gets the values for the multiple private data items in a single call |
| 169 | + GetPrivateDataMultipleKeys(namespace, collection string, keys []string) ([][]byte, error) |
| 170 | +
|
| 171 | + // GetStateMultipleKeys gets the values for multiple keys in a single call |
| 172 | + GetStateMultipleKeys(namespace string, keys []string) ([][]byte, error) |
| 173 | +
|
| 174 | + // GetTransientByTXID gets the values private data associated with the given txID |
| 175 | + GetTransientByTXID(txID string) ([]*rwset.TxPvtReadWriteSet, error) |
| 176 | +
|
| 177 | + // Done releases resources occupied by the State |
| 178 | + Done() |
| 179 | + } |
| 180 | +
|
| 181 | +Validation plugin implementation |
| 182 | +-------------------------------- |
| 183 | + |
| 184 | +To implement a validation plugin, one must implement the ``Plugin`` interface |
| 185 | +found in ``core/handlers/validation/api/validation.go``: |
| 186 | + |
| 187 | +.. code-block:: Go |
| 188 | +
|
| 189 | + // Plugin validates transactions |
| 190 | + type Plugin interface { |
| 191 | + // Validate returns nil if the action at the given position inside the transaction |
| 192 | + // at the given position in the given block is valid, or an error if not. |
| 193 | + Validate(block *common.Block, namespace string, txPosition int, actionPosition int, contextData ...ContextDatum) error |
| 194 | +
|
| 195 | + // Init injects dependencies into the instance of the Plugin |
| 196 | + Init(dependencies ...Dependency) error |
| 197 | + } |
| 198 | +
|
| 199 | +Each ``ContextDatum`` is additional runtime-derived metadata that is passed by |
| 200 | +the peer to the validation plugin. Currently, the only ``ContextDatum`` that is |
| 201 | +passed is one that represents the endorsement policy of the chaincode: |
| 202 | + |
| 203 | +.. code-block:: Go |
| 204 | +
|
| 205 | + // SerializedPolicy defines a serialized policy |
| 206 | + type SerializedPolicy interface { |
| 207 | + validation.ContextDatum |
| 208 | +
|
| 209 | + // Bytes returns the bytes of the SerializedPolicy |
| 210 | + Bytes() []byte |
| 211 | + } |
| 212 | +
|
| 213 | +A validation plugin instance of a given plugin type (identified either by the |
| 214 | +method name as an instance method of the ``HandlerLibrary`` or by the plugin ``.so`` |
| 215 | +file path) is created for each channel by having the peer invoke the ``New`` |
| 216 | +method in the ``PluginFactory`` interface which is also expected to be implemented |
| 217 | +by the plugin developer: |
| 218 | + |
| 219 | +.. code-block:: Go |
| 220 | +
|
| 221 | + // PluginFactory creates a new instance of a Plugin |
| 222 | + type PluginFactory interface { |
| 223 | + New() Plugin |
| 224 | + } |
| 225 | +
|
| 226 | +The ``Init`` method is expected to receive as input all the dependencies declared |
| 227 | +under ``core/handlers/validation/api/``, identified as embedding the ``Dependency`` |
| 228 | +interface. |
| 229 | + |
| 230 | +After the creation of the ``Plugin`` instance, the **Init** method is invoked on |
| 231 | +it by the peer with the dependencies passed as parameters. |
| 232 | + |
| 233 | +Currently Fabric comes with the following dependencies for validation plugins: |
| 234 | + |
| 235 | +- ``IdentityDeserializer``: Converts byte representation of identities into |
| 236 | + ``Identity`` objects that can be used to verify signatures signed by them, be |
| 237 | + validated themselves against their corresponding MSP, and see whether they |
| 238 | + satisfy a given **MSP Principal**. The full specification can be found in |
| 239 | + ``core/handlers/validation/api/identities/identities.go``. |
| 240 | + |
| 241 | +- ``PolicyEvaluator``: Evaluates whether a given policy is satisfied: |
| 242 | + |
| 243 | +.. code-block:: Go |
| 244 | +
|
| 245 | + // PolicyEvaluator evaluates policies |
| 246 | + type PolicyEvaluator interface { |
| 247 | + validation.Dependency |
| 248 | +
|
| 249 | + // Evaluate takes a set of SignedData and evaluates whether this set of signatures satisfies |
| 250 | + // the policy with the given bytes |
| 251 | + Evaluate(policyBytes []byte, signatureSet []*common.SignedData) error |
| 252 | + } |
| 253 | +
|
| 254 | +- ``StateFetcher``: Fetches a ``State`` object which interacts with the world state: |
| 255 | + |
| 256 | +.. code-block:: Go |
| 257 | +
|
| 258 | + // State defines interaction with the world state |
| 259 | + type State interface { |
| 260 | + // GetStateMultipleKeys gets the values for multiple keys in a single call |
| 261 | + GetStateMultipleKeys(namespace string, keys []string) ([][]byte, error) |
| 262 | +
|
| 263 | + // GetStateRangeScanIterator returns an iterator that contains all the key-values between given key ranges. |
| 264 | + // startKey is included in the results and endKey is excluded. An empty startKey refers to the first available key |
| 265 | + // and an empty endKey refers to the last available key. For scanning all the keys, both the startKey and the endKey |
| 266 | + // can be supplied as empty strings. However, a full scan should be used judiciously for performance reasons. |
| 267 | + // The returned ResultsIterator contains results of type *KV which is defined in protos/ledger/queryresult. |
| 268 | + GetStateRangeScanIterator(namespace string, startKey string, endKey string) (ResultsIterator, error) |
| 269 | +
|
| 270 | + // Done releases resources occupied by the State |
| 271 | + Done() |
| 272 | + } |
| 273 | +
|
| 274 | +Important notes |
| 275 | +--------------- |
| 276 | + |
| 277 | +- **Validation plugin consistency across peers:** In future releases, the Fabric |
| 278 | + channel infrastructure would guarantee that the same validation logic is used |
| 279 | + for a given chaincode by all peers in the channel at any given blockchain |
| 280 | + height in order to eliminate the chance of mis-configuration which would might |
| 281 | + lead to state divergence among peers that accidentally run different |
| 282 | + implementations. However, for now it is the sole responsibility of the system |
| 283 | + operators and administrators to ensure this doesn't happen. |
| 284 | + |
| 285 | +- **Validation plugin error handling:** Whenever a validation plugin can't |
| 286 | + determine whether a given transaction is valid or not, because of some transient |
| 287 | + execution problem like inability to access the database, it should return an |
| 288 | + error of type **ExecutionFailureError** that is defined in ``core/handlers/validation/api/validation.go``. |
| 289 | + Any other error that is returned, is treated as an endorsement policy error |
| 290 | + and marks the transaction as invalidated by the validation logic. However, |
| 291 | + if an ``ExecutionFailureError`` is returned, the chain processing halts instead |
| 292 | + of marking the transaction as invalid. This is to prevent state divergence |
| 293 | + between different peers. |
| 294 | + |
| 295 | +- **Importing Fabric code into the plugin**: Importing code that belongs to Fabric |
| 296 | + other than protobufs as part of the plugin is highly discouraged, and can lead |
| 297 | + to issues when the Fabric code changes between releases, or can cause inoperability |
| 298 | + issues when running mixed peer versions. Ideally, the plugin code should only |
| 299 | + use the dependencies given to it, and should import the bare minimum other |
| 300 | + than protobufs. |
| 301 | + |
| 302 | + .. Licensed under Creative Commons Attribution 4.0 International License |
| 303 | + https://creativecommons.org/licenses/by/4.0/ |
0 commit comments