Skip to content

Commit

Permalink
Merge pull request #751 from kaleido-io/fftm
Browse files Browse the repository at this point in the history
Add first step of FireFly Transaction Manager support - separate URL for submit
  • Loading branch information
peterbroadhurst authored Apr 30, 2022
2 parents 4de43ef + c68b587 commit d361cb5
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 11 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"ffimethods",
"ffresty",
"ffstruct",
"FFTM",
"fftypes",
"finalizers",
"Hyperledger",
Expand Down
36 changes: 36 additions & 0 deletions docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ nav_order: 3
|batchTimeout|How long Ethconnect should wait for new events to arrive and fill a batch, before sending the batch to FireFly core. Only applies when automatically creating a new event stream.|[`time.Duration`](https://pkg.go.dev/time#Duration)|`500`
|connectionTimeout|The maximum amount of time that a connection is allowed to remain with no data transmitted|[`time.Duration`](https://pkg.go.dev/time#Duration)|`30s`
|expectContinueTimeout|See [ExpectContinueTimeout in the Go docs](https://pkg.go.dev/net/http#Transport)|[`time.Duration`](https://pkg.go.dev/time#Duration)|`1s`
|fromBlock|The first event this FireFly instance should listen to from the BatchPin smart contract. Default=0. Only affects initial creation of the event stream|Address `string`|`0`
|headers|Adds custom headers to HTTP requests|`map[string]string`|`<nil>`
|idleTimeout|The max duration to hold a HTTP keepalive connection between calls|[`time.Duration`](https://pkg.go.dev/time#Duration)|`475ms`
|instance|The Ethereum address of the FireFly BatchPin smart contract that has been deployed to the blockchain|Address `string`|`<nil>`
Expand Down Expand Up @@ -207,6 +208,41 @@ nav_order: 3
|readBufferSize|The size in bytes of the read buffer for the WebSocket connection|[`BytesSize`](https://pkg.go.dev/github.com/docker/go-units#BytesSize)|`16Kb`
|writeBufferSize|The size in bytes of the write buffer for the WebSocket connection|[`BytesSize`](https://pkg.go.dev/github.com/docker/go-units#BytesSize)|`16Kb`

## blockchain.ethereum.fftm

|Key|Description|Type|Default Value|
|---|-----------|----|-------------|
|connectionTimeout|The maximum amount of time that a connection is allowed to remain with no data transmitted|[`time.Duration`](https://pkg.go.dev/time#Duration)|`30s`
|expectContinueTimeout|See [ExpectContinueTimeout in the Go docs](https://pkg.go.dev/net/http#Transport)|[`time.Duration`](https://pkg.go.dev/time#Duration)|`1s`
|headers|Adds custom headers to HTTP requests|`map[string]string`|`<nil>`
|idleTimeout|The max duration to hold a HTTP keepalive connection between calls|[`time.Duration`](https://pkg.go.dev/time#Duration)|`475ms`
|maxIdleConns|The max number of idle connections to hold pooled|`int`|`100`
|requestTimeout|The maximum amount of time that a request is allowed to remain open|[`time.Duration`](https://pkg.go.dev/time#Duration)|`30s`
|tlsHandshakeTimeout|The maximum amount of time to wait for a successful TLS handshake|[`time.Duration`](https://pkg.go.dev/time#Duration)|`10s`
|url|The URL of the FireFly Transaction Manager runtime, if enabled|`string`|`<nil>`

## blockchain.ethereum.fftm.auth

|Key|Description|Type|Default Value|
|---|-----------|----|-------------|
|password|Password|`string`|`<nil>`
|username|Username|`string`|`<nil>`

## blockchain.ethereum.fftm.proxy

|Key|Description|Type|Default Value|
|---|-----------|----|-------------|
|url|Optional HTTP proxy server to use when connecting to the Transaction Manager|`string`|`<nil>`

## blockchain.ethereum.fftm.retry

|Key|Description|Type|Default Value|
|---|-----------|----|-------------|
|count|The maximum number of times to retry|`int`|`5`
|enabled|Enables retries|`boolean`|`false`
|initWaitTime|The initial retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`250ms`
|maxWaitTime|The maximum retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`30s`

## blockchain.fabric.fabconnect

|Key|Description|Type|Default Value|
Expand Down
10 changes: 10 additions & 0 deletions internal/blockchain/ethereum/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
defaultBatchTimeout = 500
defaultPrefixShort = "fly"
defaultPrefixLong = "firefly"
defaultFromBlock = "0"

defaultAddressResolverMethod = "GET"
defaultAddressResolverResponseField = "address"
Expand All @@ -50,6 +51,8 @@ const (
EthconnectPrefixShort = "prefixShort"
// EthconnectPrefixLong is used in HTTP headers in requests to ethconnect
EthconnectPrefixLong = "prefixLong"
// EthconnectConfigFromBlock is the configuration of the first block to listen to when creating the listener for the FireFly contract
EthconnectConfigFromBlock = "fromBlock"

// AddressResolverConfigKey is a sub-key in the config to contain an address resolver config.
AddressResolverConfigKey = "addressResolver"
Expand All @@ -67,6 +70,9 @@ const (
AddressResolverCacheSize = "cache.size"
// AddressResolverCacheTTL the TTL on cache entries
AddressResolverCacheTTL = "cache.ttl"

// FFTMConfigKey is a sub-key in the config that optionally contains FireFly transaction connection information
FFTMConfigKey = "fftm"
)

func (e *Ethereum) InitPrefix(prefix config.Prefix) {
Expand All @@ -78,6 +84,10 @@ func (e *Ethereum) InitPrefix(prefix config.Prefix) {
ethconnectConf.AddKnownKey(EthconnectConfigBatchTimeout, defaultBatchTimeout)
ethconnectConf.AddKnownKey(EthconnectPrefixShort, defaultPrefixShort)
ethconnectConf.AddKnownKey(EthconnectPrefixLong, defaultPrefixLong)
ethconnectConf.AddKnownKey(EthconnectConfigFromBlock, defaultFromBlock)

fftmConf := prefix.SubPrefix(FFTMConfigKey)
ffresty.InitPrefix(fftmConf)

addressResolverConf := prefix.SubPrefix(AddressResolverConfigKey)
ffresty.InitPrefix(addressResolverConf)
Expand Down
18 changes: 15 additions & 3 deletions internal/blockchain/ethereum/ethereum.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type Ethereum struct {
capabilities *blockchain.Capabilities
callbacks blockchain.Callbacks
client *resty.Client
fftmClient *resty.Client
streams *streamManager
initInfo struct {
stream *eventStream
Expand Down Expand Up @@ -157,6 +158,7 @@ func (e *Ethereum) VerifierType() fftypes.VerifierType {
func (e *Ethereum) Init(ctx context.Context, prefix config.Prefix, callbacks blockchain.Callbacks, metrics metrics.Manager) (err error) {
ethconnectConf := prefix.SubPrefix(EthconnectConfigKey)
addressResolverConf := prefix.SubPrefix(AddressResolverConfigKey)
fftmConf := prefix.SubPrefix(FFTMConfigKey)

e.ctx = log.WithLogField(ctx, "proto", "ethereum")
e.callbacks = callbacks
Expand All @@ -173,6 +175,11 @@ func (e *Ethereum) Init(ctx context.Context, prefix config.Prefix, callbacks blo
}

e.client = ffresty.New(e.ctx, ethconnectConf)

if fftmConf.GetString(ffresty.HTTPConfigURL) != "" {
e.fftmClient = ffresty.New(e.ctx, fftmConf)
}

e.capabilities = &blockchain.Capabilities{
GlobalSequencer: true,
}
Expand Down Expand Up @@ -218,15 +225,16 @@ func (e *Ethereum) Init(ctx context.Context, prefix config.Prefix, callbacks blo
}

e.streams = &streamManager{
client: e.client,
client: e.client,
fireFlySubscriptionFromBlock: ethconnectConf.GetString(EthconnectConfigFromBlock),
}
batchSize := ethconnectConf.GetUint(EthconnectConfigBatchSize)
batchTimeout := uint(ethconnectConf.GetDuration(EthconnectConfigBatchTimeout).Milliseconds())
if e.initInfo.stream, err = e.streams.ensureEventStream(e.ctx, e.topic, batchSize, batchTimeout); err != nil {
return err
}
log.L(e.ctx).Infof("Event stream: %s (topic=%s)", e.initInfo.stream.ID, e.topic)
if e.initInfo.sub, err = e.streams.ensureSubscription(e.ctx, e.instancePath, e.initInfo.stream.ID, batchPinEventABI); err != nil {
if e.initInfo.sub, err = e.streams.ensureFireFlySubscription(e.ctx, e.instancePath, e.initInfo.stream.ID, batchPinEventABI); err != nil {
return err
}

Expand Down Expand Up @@ -544,8 +552,12 @@ func (e *Ethereum) invokeContractMethod(ctx context.Context, address, signingKey
Method: abi,
Params: input,
}
client := e.fftmClient
if client == nil {
client = e.client
}
var resErr ethError
res, err := e.client.R().
res, err := client.R().
SetContext(ctx).
SetBody(body).
SetError(&resErr).
Expand Down
7 changes: 5 additions & 2 deletions internal/blockchain/ethereum/ethereum_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (
var utConfPrefix = config.NewPluginConfig("eth_unit_tests")
var utEthconnectConf = utConfPrefix.SubPrefix(EthconnectConfigKey)
var utAddressResolverConf = utConfPrefix.SubPrefix(AddressResolverConfigKey)
var utFFTMConf = utConfPrefix.SubPrefix(FFTMConfigKey)

func testFFIMethod() *fftypes.FFIMethod {
return &fftypes.FFIMethod{
Expand Down Expand Up @@ -139,7 +140,7 @@ func TestInitMissingTopic(t *testing.T) {
assert.Regexp(t, "FF10138.*topic", err)
}

func TestInitAllNewStreamsAndWSEvent(t *testing.T) {
func TestInitAllNewStreamsAndWSEventWithFFTM(t *testing.T) {

log.SetLevel("trace")
e, cancel := newTestEthereum()
Expand Down Expand Up @@ -175,9 +176,11 @@ func TestInitAllNewStreamsAndWSEvent(t *testing.T) {
utEthconnectConf.Set(ffresty.HTTPCustomClient, mockedClient)
utEthconnectConf.Set(EthconnectConfigInstancePath, "/instances/0x12345")
utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
utFFTMConf.Set(ffresty.HTTPConfigURL, "http://fftm.example.com:12345")

err := e.Init(e.ctx, utConfPrefix, &blockchainmocks.Callbacks{}, &metricsmocks.Manager{})
assert.NoError(t, err)
assert.NotNil(t, e.fftmClient)

assert.Equal(t, "ethereum", e.Name())
assert.Equal(t, fftypes.VerifierTypeEthAddress, e.VerifierType())
Expand Down Expand Up @@ -1296,7 +1299,7 @@ func TestAddSubscription(t *testing.T) {
},
},
Options: &fftypes.ContractListenerOptions{
FirstEvent: string(fftypes.SubOptsFirstEventNewest),
FirstEvent: string(fftypes.SubOptsFirstEventOldest),
},
},
}
Expand Down
6 changes: 4 additions & 2 deletions internal/blockchain/ethereum/eventstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import (

type streamManager struct {
client *resty.Client

fireFlySubscriptionFromBlock string
}

type eventStream struct {
Expand Down Expand Up @@ -170,7 +172,7 @@ func (s *streamManager) deleteSubscription(ctx context.Context, subID string) er
return nil
}

func (s *streamManager) ensureSubscription(ctx context.Context, instancePath, stream string, abi ABIElementMarshaling) (sub *subscription, err error) {
func (s *streamManager) ensureFireFlySubscription(ctx context.Context, instancePath, stream string, abi ABIElementMarshaling) (sub *subscription, err error) {
// Include a hash of the instance path in the subscription, so if we ever point at a different
// contract configuration, we re-subscribe from block 0.
// We don't need full strength hashing, so just use the first 16 chars for readability.
Expand Down Expand Up @@ -198,7 +200,7 @@ func (s *streamManager) ensureSubscription(ctx context.Context, instancePath, st
}

if sub == nil {
if sub, err = s.createSubscription(ctx, location, stream, subName, string(fftypes.SubOptsFirstEventOldest), abi); err != nil {
if sub, err = s.createSubscription(ctx, location, stream, subName, s.fireFlySubscriptionFromBlock, abi); err != nil {
return nil, err
}
}
Expand Down
4 changes: 4 additions & 0 deletions internal/coremsgs/en_config_descriptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,15 @@ var (
ConfigBlockchainEthereumEthconnectBatchSize = ffc("config.blockchain.ethereum.ethconnect.batchSize", "The number of events Ethconnect should batch together for delivery to FireFly core. Only applies when automatically creating a new event stream.", i18n.IntType)
ConfigBlockchainEthereumEthconnectBatchTimeout = ffc("config.blockchain.ethereum.ethconnect.batchTimeout", "How long Ethconnect should wait for new events to arrive and fill a batch, before sending the batch to FireFly core. Only applies when automatically creating a new event stream.", i18n.TimeDurationType)
ConfigBlockchainEthereumEthconnectInstance = ffc("config.blockchain.ethereum.ethconnect.instance", "The Ethereum address of the FireFly BatchPin smart contract that has been deployed to the blockchain", "Address "+i18n.StringType)
ConfigBlockchainEthereumEthconnectFromBlock = ffc("config.blockchain.ethereum.ethconnect.fromBlock", "The first event this FireFly instance should listen to from the BatchPin smart contract. Default=0. Only affects initial creation of the event stream", "Address "+i18n.StringType)
ConfigBlockchainEthereumEthconnectPrefixLong = ffc("config.blockchain.ethereum.ethconnect.prefixLong", "The prefix that will be used for Ethconnect specific HTTP headers when FireFly makes requests to Ethconnect", i18n.StringType)
ConfigBlockchainEthereumEthconnectPrefixShort = ffc("config.blockchain.ethereum.ethconnect.prefixShort", "The prefix that will be used for Ethconnect specific query parameters when FireFly makes requests to Ethconnect", i18n.StringType)
ConfigBlockchainEthereumEthconnectTopic = ffc("config.blockchain.ethereum.ethconnect.topic", "The websocket listen topic that the node should register on, which is important if there are multiple nodes using a single ethconnect", i18n.StringType)
ConfigBlockchainEthereumEthconnectURL = ffc("config.blockchain.ethereum.ethconnect.url", "The URL of the Ethconnect instance", "URL "+i18n.StringType)

ConfigBlockchainEthereumFFTMURL = ffc("config.blockchain.ethereum.fftm.url", "The URL of the FireFly Transaction Manager runtime, if enabled", i18n.StringType)
ConfigBlockchainEthereumFFTMProxyURL = ffc("config.blockchain.ethereum.fftm.proxy.url", "Optional HTTP proxy server to use when connecting to the Transaction Manager", i18n.StringType)

ConfigBlockchainEthereumEthconnectProxyURL = ffc("config.blockchain.ethereum.ethconnect.proxy.url", "Optional HTTP proxy server to use when connecting to Ethconnect", "URL "+i18n.StringType)

ConfigBlockchainFabricFabconnectBatchSize = ffc("config.blockchain.fabric.fabconnect.batchSize", "The number of events Fabconnect should batch together for delivery to FireFly core. Only applies when automatically creating a new event stream.", i18n.IntType)
Expand Down
4 changes: 3 additions & 1 deletion smart_contracts/ethereum/solidity_firefly/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
node_modules
package-lock.json
artifacts
cache
cache
typechain-types
32 changes: 32 additions & 0 deletions smart_contracts/ethereum/solidity_firefly/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { HardhatUserConfig } from 'hardhat/config';
import '@nomiclabs/hardhat-etherscan';
import '@nomiclabs/hardhat-waffle';
import '@typechain/hardhat';
import 'hardhat-gas-reporter';
import 'solidity-coverage';

// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more

const config: HardhatUserConfig = {
solidity: "0.8.4",
defaultNetwork: "firefly-cli",
networks: {
'firefly-cli': {
url: "http://127.0.0.1:5100"
},
rinkeby: {
url: `https://rinkeby.infura.io/v3/${process.env.INFURA_PROJECT_ID}`,
accounts: [`${process.env.RINKEBY_PRIVATE_KEY}`]
}
},
gasReporter: {
enabled: process.env.REPORT_GAS !== undefined,
currency: 'USD',
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
};

export default config;
18 changes: 15 additions & 3 deletions smart_contracts/ethereum/solidity_firefly/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
{
"name": "@hyperledger/assettrail-contracts",
"name": "@hyperledger/firefly-contracts",
"version": "0.0.1",
"dependencies": {
"truffle": "^5.4.32"
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.5",
"@nomiclabs/hardhat-etherscan": "^3.0.3",
"@nomiclabs/hardhat-waffle": "^2.0.3",
"@typechain/ethers-v5": "^10.0.0",
"@typechain/hardhat": "^6.0.0",
"ethers": "^5.6.4",
"hardhat": "^2.9.3",
"hardhat-gas-reporter": "^1.0.8",
"solidity-coverage": "^0.7.20",
"truffle": "^5.5.10",
"ts-node": "^10.7.0",
"typechain": "^8.0.0",
"typescript": "^4.6.3"
}
}
28 changes: 28 additions & 0 deletions smart_contracts/ethereum/solidity_firefly/scripts/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// When running the script with `npx hardhat run <script>` you'll find the Hardhat
// Runtime Environment's members available in the global scope.
import { ethers } from "hardhat";

async function main() {
// Hardhat always runs the compile task when running scripts with its command
// line interface.
//
// If this script is run directly using `node` you may want to call compile
// manually to make sure everything is compiled
// await hre.run('compile');

// We get the contract to deploy
const Firefly = await ethers.getContractFactory("Firefly");
const firefly = await Firefly.deploy();
await firefly.deployed();
console.log("FireFly contract deployed to:", firefly.address);
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

0 comments on commit d361cb5

Please sign in to comment.