Skip to content

Commit f29dbad

Browse files
Merge pull request #25 from kaleido-io/issue-24
Add delete functionality with policy engine involvement
2 parents 95172e6 + 44b1a60 commit f29dbad

20 files changed

+567
-58
lines changed

README.md

+88-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
# Hyperledger FireFly Transaction Manager
44

5-
Plugable microservice component of Hyperledger FireFly, responsible for:
5+
The core component of the FireFly Connector Framework for Blockchains, responsible for:
66

77
- Submission of transactions to blockchains of all types
8+
- Nonce management - idempotent submission of transactions, and assignment of nonces
89
- Protocol connectivity decoupled with additional lightweight API connector
910
- Easy to add additional protocols that conform to normal patterns of TX submission / events
1011

@@ -16,12 +17,96 @@ Plugable microservice component of Hyperledger FireFly, responsible for:
1617
- Extensible policy engine
1718
- Gas station API integration
1819

19-
- Event streaming [* work in progress to extract from previous location in ethconnect]
20+
- Event streaming
2021
- Protocol agnostic event polling/streaming support
2122
- Reliable checkpoint restart
2223
- At least once delivery API
2324

24-
![Hyperledger FireFly Transaction Manager](./images/firefly_transaction_manager.jpg)
25+
## Architecture
26+
27+
The core architecture of the FireFly Connector Framework is as follows:
28+
29+
[![Hyperledger FireFly Transaction Manager](./images/firefly_connector_framework_architecture.jpg)](./images/firefly_connector_framework_architecture.jpg)
30+
31+
This re-usable codebase contains as much as possible of the re-usable heavy lifting code needed across any blockchain.
32+
33+
The framework is currently constrained to blockchains that adhere to certain basic principals:
34+
35+
1. Has transactions
36+
- That are signed
37+
- That can optionally have gas semantics (limits and prices, expressed in a blockchain specific way)
38+
2. Has events (or "logs")
39+
- That are emitted as a deterministic outcome of transactions
40+
3. Has blocks
41+
- Containing zero or more transactions, with their associated events
42+
- With a sequential numeric order
43+
- With a hash
44+
- With a parent hash
45+
4. Has finality for transactions & events that can be expressed as a level of confidence over time
46+
- Confirmations: A number of sequential blocks in the canonical chain that contain the transaction
47+
48+
## Nonce management
49+
50+
The nonces for transactions is assigned as early as possible in the flow:
51+
- Before the REST API for submission of the transaction occurs
52+
- After the FFCAPI blockchain connector verifies the transaction can be encoded successfully to the chain
53+
- With protection against multiple parallel API requests for the same signing address
54+
- With stateful persistence meaning the connector knows about all nonces it previously allocated, to avoids duplicates
55+
56+
This "at source" allocation of nonces provides the strictest assurance of order of transactions possible,
57+
because the order is locked in with the coordination of the business logic of the application submitting the transaction.
58+
59+
As well as protecting against loss of transactions, this protects against duplication of transactions - even in crash
60+
recovery scenarios with a sufficiently reliable persistence layer.
61+
62+
### Avoid multiple nonce management systems against the same signing key
63+
64+
FFTM is optimized for cases where all transactions for a given signing address flow through the
65+
same FireFly connector. If you have signing and nonce allocation happening elsewhere, not going through the
66+
FireFly blockchain connector, then it is possible that the same nonce will be allocated in two places.
67+
68+
> Be careful that the signing keys for transactions you stream through the Nonce Management of the FireFly
69+
> blockchain connector are not used elsewhere.
70+
71+
If you must have multiple systems performing nonce management against the same keys you use with FireFly nonce management,
72+
you can set the `transactions.nonceStateTimeout` to `0` (or a low threshold like `100ms`) to cause the nonce management
73+
to query the pending transaction pool of the node every time a nonce is allocated.
74+
75+
This reduces the window for concurrent nonce allocation to be small (basically the same as if you had
76+
multiple simple web/mobile wallets used against the same key), but it does not eliminate it completely it.
77+
78+
### Why "at source" nonce management was chosen vs. "at target"
79+
80+
The "at source" approach to ordering used in FFTM could be compared with the "at target" allocation of nonces used in
81+
[EthConnect](https://github.com/hyperledger/firefly-ethconnect)).
82+
83+
The "at target" approach optimizes for throughput and ability to send new transactions to the chain,
84+
with an at-least-once delivery assurance to the applications.
85+
86+
An "at target" algorithm as used in EthConnect could resume transaction delivery automatically without operator intervention
87+
from almost all scenarios, including where nonces have been double allocated.
88+
89+
However, "at target" comes with two compromises that mean FFTM chose the "at source" approach was chosen for FFTM:
90+
91+
- Individual transactions might fail in certain scenarios, and subsequent transactions will still be streamed to the chain.
92+
While desirable for automation and throughput, this reduces the ordering guarantee for high value transactions.
93+
94+
- In crash recovery scenarios the assurance is at-least-once delivery for "at target" ordering (rather than "exactly once"),
95+
although the window can be made very small through various optimizations included in the EthConnect codebase.
96+
97+
## Policy Manager
98+
99+
> TODO: Add more detail to describe the pluggability of the Policy Manager component, to perform transaction gas price
100+
> estimation, advanced monitoring of transactions, submission and re-submission of the transactions with updated
101+
> parameters (such as gas price) etc.
102+
103+
## Event streaming
104+
105+
One of the most sophisticated parts of the FireFly Connector Framework is the handling of event streams.
106+
107+
> TODO: More detail to back up this diagram.
108+
109+
[![Event Streams](./images/fftm_event_streams_architecture.jpg)](./images/fftm_event_streams_architecture.jpg)
25110

26111
# Configuration
27112

592 KB
Loading
Loading
-515 KB
Binary file not shown.

internal/tmmsgs/en_api_descriptions.go

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ var (
3636
APIEndpointGetEventStreams = ffm("api.endpoints.get.eventstreams", "List event streams")
3737
APIEndpointGetEventStream = ffm("api.endpoints.get.eventstream", "Get an event stream with status")
3838
APIEndpointDeleteEventStream = ffm("api.endpoints.delete.eventstream", "Delete an event stream")
39+
APIEndpointDeleteTransaction = ffm("api.endpoints.delete.transaction", "Request transaction deletion by the policy engine. Result could be immediate (200), asynchronous (202), or rejected with an error")
3940
APIEndpointGetSubscriptions = ffm("api.endpoints.get.subscriptions", "Get listeners - route deprecated in favor of /eventstreams/{streamId}/listeners")
4041
APIEndpointGetSubscription = ffm("api.endpoints.get.subscription", "Get listener - route deprecated in favor of /eventstreams/{streamId}/listeners/{listenerId}")
4142
APIEndpointPostSubscriptions = ffm("api.endpoints.post.subscriptions", "Create new listener - route deprecated in favor of /eventstreams/{streamId}/listeners")

internal/tmmsgs/en_error_messges.go

+3
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,7 @@ var (
8282
MsgInvalidSortDirection = ffe("FF21064", "Sort direction must be 'asc'/'ascending' or 'desc'/'descending': '%s'", http.StatusBadRequest)
8383
MsgDuplicateID = ffe("FF21065", "ID '%s' is not unique", http.StatusConflict)
8484
MsgTransactionFailed = ffe("FF21066", "Transaction execution failed")
85+
MsgTransactionNotFound = ffe("FF21067", "Transaction '%s' not found", http.StatusNotFound)
86+
MsgPolicyEngineRequestTimeout = ffe("FF21068", "The policy engine did not acknowledge the request after %.2fs", 408)
87+
MsgPolicyEngineRequestInvalid = ffe("FF21069", "Invalid policy engine request type '%d'")
8588
)

mocks/policyenginemocks/policy_engine.go

+6-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/apitypes/managed_tx.go

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ type ManagedTX struct {
6161
Created *fftypes.FFTime `json:"created"`
6262
Updated *fftypes.FFTime `json:"updated"`
6363
Status TxStatus `json:"status"`
64+
DeleteRequested *fftypes.FFTime `json:"deleteRequested,omitempty"`
6465
SequenceID *fftypes.UUID `json:"sequenceId"`
6566
Nonce *fftypes.FFBigInt `json:"nonce"`
6667
Gas *fftypes.FFBigInt `json:"gas"`

pkg/fftm/manager.go

+29-8
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,26 @@ type Manager interface {
4444
Close()
4545
}
4646

47+
type policyEngineAPIRequestType int
48+
49+
const (
50+
policyEngineAPIRequestTypeDelete policyEngineAPIRequestType = iota
51+
)
52+
53+
// policyEngineAPIRequest requests are queued to the policy engine thread for processing against a given Transaction
54+
type policyEngineAPIRequest struct {
55+
requestType policyEngineAPIRequestType
56+
txID string
57+
startTime time.Time
58+
response chan policyEngineAPIResponse
59+
}
60+
61+
type policyEngineAPIResponse struct {
62+
tx *apitypes.ManagedTX
63+
err error
64+
status int // http status code (200 Ok vs. 202 Accepted) - only set for success cases
65+
}
66+
4767
type manager struct {
4868
ctx context.Context
4969
cancelCtx func()
@@ -58,14 +78,15 @@ type manager struct {
5878
inflightUpdate chan bool
5979
inflight []*pendingState
6080

61-
mux sync.Mutex
62-
lockedNonces map[string]*lockedNonce
63-
eventStreams map[fftypes.UUID]events.Stream
64-
streamsByName map[string]*fftypes.UUID
65-
policyLoopDone chan struct{}
66-
blockListenerDone chan struct{}
67-
started bool
68-
apiServerDone chan error
81+
mux sync.Mutex
82+
policyEngineAPIRequests []*policyEngineAPIRequest
83+
lockedNonces map[string]*lockedNonce
84+
eventStreams map[fftypes.UUID]events.Stream
85+
streamsByName map[string]*fftypes.UUID
86+
policyLoopDone chan struct{}
87+
blockListenerDone chan struct{}
88+
started bool
89+
apiServerDone chan error
6990

7091
policyLoopInterval time.Duration
7192
nonceStateTimeout time.Duration

0 commit comments

Comments
 (0)