Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add first step of FireFly Transaction Manager support - separate URL for submit #751

Merged
merged 11 commits into from
Apr 30, 2022
Merged
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;
});