diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 0e11933747..f35ae07ff5 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -26,6 +26,17 @@ jobs: e2e-test: runs-on: ubuntu-latest + strategy: + matrix: + blockchain-provider: [geth, fabric] + database-type: [sqlite3] + test-suite: [TestEthereumE2ESuite, TestFabricE2ESuite] + exclude: + - blockchain-provider: geth + test-suite: TestFabricE2ESuite + - blockchain-provider: fabric + test-suite: TestEthereumE2ESuite + fail-fast: false steps: - uses: actions/checkout@v2 @@ -35,6 +46,10 @@ jobs: go-version: 1.16 - name: Run E2E tests + env: + TEST_SUITE: ${{ matrix.test-suite }} + BLOCKCHAIN_PROVIDER: ${{ matrix.blockchain-provider }} + DATABASE_TYPE: ${{ matrix.database-type }} run: ./test/e2e/run.sh - name: Archive container logs diff --git a/internal/blockchain/fabric/fabric_test.go b/internal/blockchain/fabric/fabric_test.go index 1a837fde90..05dd3c4a17 100644 --- a/internal/blockchain/fabric/fabric_test.go +++ b/internal/blockchain/fabric/fabric_test.go @@ -513,7 +513,7 @@ func TestResolveSignerBadECertReturned(t *testing.T) { responder, _ := httpmock.NewJsonResponder(200, res) httpmock.RegisterResponder("GET", `http://localhost:12345/identities/signer001`, responder) _, err := e.ResolveSigningKey(context.Background(), "signer001") - assert.EqualError(t, err, "FF10286: Failed to decode certificate: asn1: syntax error: data truncated") + assert.Contains(t, err.Error(), "FF10286: Failed to decode certificate:") } func TestResolveSignerBadCACertReturned(t *testing.T) { @@ -531,7 +531,7 @@ func TestResolveSignerBadCACertReturned(t *testing.T) { responder, _ := httpmock.NewJsonResponder(200, res) httpmock.RegisterResponder("GET", `http://localhost:12345/identities/signer001`, responder) _, err := e.ResolveSigningKey(context.Background(), "signer001") - assert.EqualError(t, err, "FF10286: Failed to decode certificate: asn1: syntax error: data truncated") + assert.Contains(t, err.Error(), "FF10286: Failed to decode certificate:") } func TestGetUserNameWithMatches(t *testing.T) { diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 1fde4872b6..e2c2ddd6e3 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -17,19 +17,14 @@ package e2e import ( - "bytes" "context" - "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/json" "fmt" - "image/png" - "math/big" "net/http" "net/url" "os" - "strings" "testing" "time" @@ -38,8 +33,6 @@ import ( "github.com/hyperledger/firefly/pkg/fftypes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - image2ascii "github.com/qeesung/image2ascii/convert" ) type testState struct { @@ -274,576 +267,3 @@ func wsReader(t *testing.T, conn *websocket.Conn) (chan *fftypes.EventDelivery, }() return events, changeEvents } - -func TestE2EBroadcast(t *testing.T) { - - ts := beforeE2ETest(t) - defer ts.done() - - received1, changes1 := wsReader(t, ts.ws1) - received2, changes2 := wsReader(t, ts.ws2) - - var resp *resty.Response - value := fftypes.Byteable(`"Hello"`) - data := fftypes.DataRefOrValue{ - Value: value, - } - - resp, err := BroadcastMessage(ts.client1, &data, false) - require.NoError(t, err) - assert.Equal(t, 202, resp.StatusCode()) - - <-received1 - <-changes1 // also expect database change events - val1 := validateReceivedMessages(ts, ts.client1, fftypes.MessageTypeBroadcast, fftypes.TransactionTypeBatchPin, 1, 0) - assert.Equal(t, data.Value, val1) - - <-received2 - <-changes2 // also expect database change events - val2 := validateReceivedMessages(ts, ts.client2, fftypes.MessageTypeBroadcast, fftypes.TransactionTypeBatchPin, 1, 0) - assert.Equal(t, data.Value, val2) - -} - -func TestE2EPrivate(t *testing.T) { - - ts := beforeE2ETest(t) - defer ts.done() - - received1, _ := wsReader(t, ts.ws1) - received2, _ := wsReader(t, ts.ws2) - - var resp *resty.Response - value := fftypes.Byteable(`"Hello"`) - data := fftypes.DataRefOrValue{ - Value: value, - } - - resp, err := PrivateMessage(t, ts.client1, &data, []string{ - ts.org1.Name, - ts.org2.Name, - }, "", fftypes.TransactionTypeBatchPin, false) - require.NoError(t, err) - assert.Equal(t, 202, resp.StatusCode()) - - <-received1 - val1 := validateReceivedMessages(ts, ts.client1, fftypes.MessageTypePrivate, fftypes.TransactionTypeBatchPin, 1, 0) - assert.Equal(t, data.Value, val1) - - <-received2 - val2 := validateReceivedMessages(ts, ts.client2, fftypes.MessageTypePrivate, fftypes.TransactionTypeBatchPin, 1, 0) - assert.Equal(t, data.Value, val2) -} - -func TestStrongDatatypesBroadcast(t *testing.T) { - - ts := beforeE2ETest(t) - defer ts.done() - - var resp *resty.Response - value := fftypes.Byteable(`"Hello"`) - randVer, _ := rand.Int(rand.Reader, big.NewInt(100000000)) - version := fmt.Sprintf("0.0.%d", randVer.Int64()) - data := fftypes.DataRefOrValue{ - Value: value, - Datatype: &fftypes.DatatypeRef{ - Name: "widget", - Version: version, - }, - } - - // Should be rejected as datatype not known - resp, err := BroadcastMessage(ts.client1, &data, true) - require.NoError(t, err) - assert.Equal(t, 400, resp.StatusCode()) - assert.Contains(t, resp.String(), "FF10195") // datatype not found - - dt := &fftypes.Datatype{ - Name: "widget", - Version: version, - Value: widgetSchemaJSON, - } - dt = CreateDatatype(t, ts.client1, dt, true) - - resp, err = BroadcastMessage(ts.client1, &data, true) - require.NoError(t, err) - assert.Equal(t, 400, resp.StatusCode()) - assert.Contains(t, resp.String(), "FF10198") // does not conform - - data.Value = fftypes.Byteable(`{ - "id": "widget12345", - "name": "mywidget" - }`) - - resp, err = BroadcastMessage(ts.client1, &data, true) - require.NoError(t, err) - assert.Equal(t, 200, resp.StatusCode()) - -} - -func TestStrongDatatypesPrivate(t *testing.T) { - - ts := beforeE2ETest(t) - defer ts.done() - - var resp *resty.Response - value := fftypes.Byteable(`{"foo":"bar"}`) - randVer, _ := rand.Int(rand.Reader, big.NewInt(100000000)) - version := fmt.Sprintf("0.0.%d", randVer.Int64()) - data := fftypes.DataRefOrValue{ - Value: value, - Datatype: &fftypes.DatatypeRef{ - Name: "widget", - Version: version, - }, - } - - // Should be rejected as datatype not known - resp, err := PrivateMessage(t, ts.client1, &data, []string{ - ts.org1.Name, - ts.org2.Name, - }, "", fftypes.TransactionTypeBatchPin, true) - require.NoError(t, err) - assert.Equal(t, 400, resp.StatusCode()) - assert.Contains(t, resp.String(), "FF10195") // datatype not found - - dt := &fftypes.Datatype{ - Name: "widget", - Version: version, - Value: widgetSchemaJSON, - } - dt = CreateDatatype(t, ts.client1, dt, true) - - resp, err = PrivateMessage(t, ts.client1, &data, []string{ - ts.org1.Name, - ts.org2.Name, - }, "", fftypes.TransactionTypeBatchPin, false) - require.NoError(t, err) - assert.Equal(t, 400, resp.StatusCode()) - assert.Contains(t, resp.String(), "FF10198") // does not conform - - data.Value = fftypes.Byteable(`{ - "id": "widget12345", - "name": "mywidget" - }`) - - resp, err = PrivateMessage(t, ts.client1, &data, []string{ - ts.org1.Name, - ts.org2.Name, - }, "", fftypes.TransactionTypeBatchPin, true) - require.NoError(t, err) - assert.Equal(t, 200, resp.StatusCode()) - -} - -func TestE2EBroadcastBlob(t *testing.T) { - - ts := beforeE2ETest(t) - defer ts.done() - - received1, _ := wsReader(t, ts.ws1) - received2, _ := wsReader(t, ts.ws2) - - var resp *resty.Response - - resp, err := BroadcastBlobMessage(t, ts.client1) - require.NoError(t, err) - assert.Equal(t, 202, resp.StatusCode()) - - <-received1 - val1 := validateReceivedMessages(ts, ts.client1, fftypes.MessageTypeBroadcast, fftypes.TransactionTypeBatchPin, 1, 0) - assert.Regexp(t, "myfile.txt", string(val1)) - - <-received2 - val2 := validateReceivedMessages(ts, ts.client2, fftypes.MessageTypeBroadcast, fftypes.TransactionTypeBatchPin, 1, 0) - assert.Regexp(t, "myfile.txt", string(val2)) - -} - -func TestE2EPrivateBlobDatatypeTagged(t *testing.T) { - - ts := beforeE2ETest(t) - defer ts.done() - - received1, _ := wsReader(t, ts.ws1) - received2, _ := wsReader(t, ts.ws2) - - var resp *resty.Response - - resp, err := PrivateBlobMessageDatatypeTagged(t, ts.client1, []string{ - ts.org1.Name, - ts.org2.Name, - }) - require.NoError(t, err) - assert.Equal(t, 202, resp.StatusCode()) - - <-received1 - _ = validateReceivedMessages(ts, ts.client1, fftypes.MessageTypePrivate, fftypes.TransactionTypeBatchPin, 1, 0) - - <-received2 - _ = validateReceivedMessages(ts, ts.client2, fftypes.MessageTypePrivate, fftypes.TransactionTypeBatchPin, 1, 0) -} - -func TestE2EFungibleTokensAsync(t *testing.T) { - - ts := beforeE2ETest(t) - defer ts.done() - - received1, _ := wsReader(t, ts.ws1) - received2, _ := wsReader(t, ts.ws2) - - pools := GetTokenPools(t, ts.client1, time.Unix(0, 0)) - poolName := fmt.Sprintf("pool%d", len(pools)) - t.Logf("Pool name: %s", poolName) - - pool := &fftypes.TokenPool{ - Name: poolName, - Type: fftypes.TokenTypeFungible, - } - CreateTokenPool(t, ts.client1, pool, false) - - <-received1 - pools = GetTokenPools(t, ts.client1, ts.startTime) - assert.Equal(t, 1, len(pools)) - assert.Equal(t, "default", pools[0].Namespace) - assert.Equal(t, "erc1155", pools[0].Connector) - assert.Equal(t, poolName, pools[0].Name) - assert.Equal(t, fftypes.TokenTypeFungible, pools[0].Type) - assert.NotEmpty(t, pools[0].ProtocolID) - - <-received2 - pools = GetTokenPools(t, ts.client1, ts.startTime) - assert.Equal(t, 1, len(pools)) - assert.Equal(t, "default", pools[0].Namespace) - assert.Equal(t, "erc1155", pools[0].Connector) - assert.Equal(t, poolName, pools[0].Name) - assert.Equal(t, fftypes.TokenTypeFungible, pools[0].Type) - assert.NotEmpty(t, pools[0].ProtocolID) - - transfer := &fftypes.TokenTransferInput{} - transfer.Amount.Int().SetInt64(1) - MintTokens(t, ts.client1, poolName, transfer, false) - - <-received1 - transfers := GetTokenTransfers(t, ts.client1, poolName) - assert.Equal(t, 1, len(transfers)) - assert.Equal(t, "erc1155", transfers[0].Connector) - assert.Equal(t, fftypes.TokenTransferTypeMint, transfers[0].Type) - assert.Equal(t, int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(t, ts.client1, poolName, "", map[string]int64{ - ts.org1.Identity: 1, - }) - - <-received2 - transfers = GetTokenTransfers(t, ts.client2, poolName) - assert.Equal(t, 1, len(transfers)) - assert.Equal(t, "erc1155", transfers[0].Connector) - assert.Equal(t, fftypes.TokenTransferTypeMint, transfers[0].Type) - assert.Equal(t, int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(t, ts.client2, poolName, "", map[string]int64{ - ts.org1.Identity: 1, - }) - - transfer = &fftypes.TokenTransferInput{ - TokenTransfer: fftypes.TokenTransfer{ - To: ts.org2.Identity, - }, - Message: &fftypes.MessageInOut{ - InlineData: fftypes.InlineData{ - { - Value: fftypes.Byteable(`"payment for data"`), - }, - }, - }, - } - transfer.Amount.Int().SetInt64(1) - TransferTokens(t, ts.client1, poolName, transfer, false) - - <-received1 // one event for transfer - <-received1 // one event for message - transfers = GetTokenTransfers(t, ts.client1, poolName) - assert.Equal(t, 2, len(transfers)) - assert.Equal(t, "erc1155", transfers[0].Connector) - assert.Equal(t, fftypes.TokenTransferTypeTransfer, transfers[0].Type) - assert.Equal(t, int64(1), transfers[0].Amount.Int().Int64()) - data := GetDataForMessage(t, ts.client1, ts.startTime, transfers[0].MessageHash) - assert.Equal(t, 1, len(data)) - assert.Equal(t, `"payment for data"`, data[0].Value.String()) - validateAccountBalances(t, ts.client1, poolName, "", map[string]int64{ - ts.org1.Identity: 0, - ts.org2.Identity: 1, - }) - - <-received2 // one event for transfer - <-received2 // one event for message - transfers = GetTokenTransfers(t, ts.client2, poolName) - assert.Equal(t, 2, len(transfers)) - assert.Equal(t, "erc1155", transfers[0].Connector) - assert.Equal(t, fftypes.TokenTransferTypeTransfer, transfers[0].Type) - assert.Equal(t, int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(t, ts.client2, poolName, "", map[string]int64{ - ts.org1.Identity: 0, - ts.org2.Identity: 1, - }) - - transfer = &fftypes.TokenTransferInput{} - transfer.Amount.Int().SetInt64(1) - BurnTokens(t, ts.client2, poolName, transfer, false) - - <-received2 - transfers = GetTokenTransfers(t, ts.client2, poolName) - assert.Equal(t, 3, len(transfers)) - assert.Equal(t, "erc1155", transfers[0].Connector) - assert.Equal(t, fftypes.TokenTransferTypeBurn, transfers[0].Type) - assert.Equal(t, "", transfers[0].TokenIndex) - assert.Equal(t, int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(t, ts.client2, poolName, "", map[string]int64{ - ts.org1.Identity: 0, - ts.org2.Identity: 0, - }) - - <-received1 - transfers = GetTokenTransfers(t, ts.client1, poolName) - assert.Equal(t, 3, len(transfers)) - assert.Equal(t, "erc1155", transfers[0].Connector) - assert.Equal(t, fftypes.TokenTransferTypeBurn, transfers[0].Type) - assert.Equal(t, "", transfers[0].TokenIndex) - assert.Equal(t, int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(t, ts.client1, poolName, "", map[string]int64{ - ts.org1.Identity: 0, - ts.org2.Identity: 0, - }) -} - -func TestE2ENonFungibleTokensSync(t *testing.T) { - - ts := beforeE2ETest(t) - defer ts.done() - - received1, _ := wsReader(t, ts.ws1) - received2, _ := wsReader(t, ts.ws2) - - pools := GetTokenPools(t, ts.client1, time.Unix(0, 0)) - poolName := fmt.Sprintf("pool%d", len(pools)) - t.Logf("Pool name: %s", poolName) - - pool := &fftypes.TokenPool{ - Name: poolName, - Type: fftypes.TokenTypeNonFungible, - } - poolOut := CreateTokenPool(t, ts.client1, pool, true) - assert.Equal(t, "default", poolOut.Namespace) - assert.Equal(t, poolName, poolOut.Name) - assert.Equal(t, fftypes.TokenTypeNonFungible, poolOut.Type) - assert.NotEmpty(t, poolOut.ProtocolID) - - <-received1 - <-received2 - pools = GetTokenPools(t, ts.client1, ts.startTime) - assert.Equal(t, 1, len(pools)) - assert.Equal(t, "default", pools[0].Namespace) - assert.Equal(t, poolName, pools[0].Name) - assert.Equal(t, fftypes.TokenTypeNonFungible, pools[0].Type) - assert.NotEmpty(t, pools[0].ProtocolID) - - transfer := &fftypes.TokenTransferInput{} - transfer.Amount.Int().SetInt64(1) - transferOut := MintTokens(t, ts.client1, poolName, transfer, true) - assert.Equal(t, fftypes.TokenTransferTypeMint, transferOut.Type) - assert.Equal(t, "1", transferOut.TokenIndex) - assert.Equal(t, int64(1), transferOut.Amount.Int().Int64()) - validateAccountBalances(t, ts.client1, poolName, "1", map[string]int64{ - ts.org1.Identity: 1, - }) - - <-received1 - <-received2 - transfers := GetTokenTransfers(t, ts.client2, poolName) - assert.Equal(t, 1, len(transfers)) - assert.Equal(t, fftypes.TokenTransferTypeMint, transfers[0].Type) - assert.Equal(t, "1", transfers[0].TokenIndex) - assert.Equal(t, int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(t, ts.client2, poolName, "1", map[string]int64{ - ts.org1.Identity: 1, - }) - - transfer = &fftypes.TokenTransferInput{ - TokenTransfer: fftypes.TokenTransfer{ - TokenIndex: "1", - To: ts.org2.Identity, - }, - Message: &fftypes.MessageInOut{ - InlineData: fftypes.InlineData{ - { - Value: fftypes.Byteable(`"ownership change"`), - }, - }, - }, - } - transfer.Amount.Int().SetInt64(1) - transferOut = TransferTokens(t, ts.client1, poolName, transfer, true) - assert.Equal(t, fftypes.TokenTransferTypeTransfer, transferOut.Type) - assert.Equal(t, "1", transferOut.TokenIndex) - assert.Equal(t, int64(1), transferOut.Amount.Int().Int64()) - data := GetDataForMessage(t, ts.client1, ts.startTime, transferOut.MessageHash) - assert.Equal(t, 1, len(data)) - assert.Equal(t, `"ownership change"`, data[0].Value.String()) - validateAccountBalances(t, ts.client1, poolName, "1", map[string]int64{ - ts.org1.Identity: 0, - ts.org2.Identity: 1, - }) - - <-received1 // one event for transfer - <-received1 // one event for message - <-received2 // one event for transfer - <-received2 // one event for message - transfers = GetTokenTransfers(t, ts.client2, poolName) - assert.Equal(t, 2, len(transfers)) - assert.Equal(t, fftypes.TokenTransferTypeTransfer, transfers[0].Type) - assert.Equal(t, "1", transfers[0].TokenIndex) - assert.Equal(t, int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(t, ts.client2, poolName, "1", map[string]int64{ - ts.org1.Identity: 0, - ts.org2.Identity: 1, - }) - - transfer = &fftypes.TokenTransferInput{ - TokenTransfer: fftypes.TokenTransfer{ - TokenIndex: "1", - }, - } - transfer.Amount.Int().SetInt64(1) - transferOut = BurnTokens(t, ts.client2, poolName, transfer, true) - assert.Equal(t, fftypes.TokenTransferTypeBurn, transferOut.Type) - assert.Equal(t, "1", transferOut.TokenIndex) - assert.Equal(t, int64(1), transferOut.Amount.Int().Int64()) - validateAccountBalances(t, ts.client2, poolName, "1", map[string]int64{ - ts.org1.Identity: 0, - ts.org2.Identity: 0, - }) - - <-received2 - <-received1 - transfers = GetTokenTransfers(t, ts.client1, poolName) - assert.Equal(t, 3, len(transfers)) - assert.Equal(t, fftypes.TokenTransferTypeBurn, transfers[0].Type) - assert.Equal(t, "1", transfers[0].TokenIndex) - assert.Equal(t, int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(t, ts.client1, poolName, "1", map[string]int64{ - ts.org1.Identity: 0, - ts.org2.Identity: 0, - }) -} - -func TestE2EWebhookExchange(t *testing.T) { - - ts := beforeE2ETest(t) - defer ts.done() - - received1, _ := wsReader(t, ts.ws1) - received2, _ := wsReader(t, ts.ws2) - - subJSON := `{ - "transport": "webhooks", - "namespace": "default", - "name": "myhook", - "options": { - "withData": true, - "url": "https://raw.githubusercontent.com/hyperledger/firefly/main/test/data/config/firefly.core.yaml", - "reply": true, - "replytag": "myreply", - "method": "GET" - }, - "filter": { - "tag": "myrequest" - } - }` - CleanupExistingSubscription(t, ts.client2, "default", "myhook") - sub := CreateSubscription(t, ts.client2, subJSON, 201) - assert.NotNil(t, sub.ID) - - data := fftypes.DataRefOrValue{ - Value: fftypes.Byteable(`{}`), - } - - var resp *resty.Response - resp, err := PrivateMessage(t, ts.client1, &data, []string{ - ts.org1.Name, - ts.org2.Name, - }, "myrequest", fftypes.TransactionTypeBatchPin, false) - require.NoError(t, err) - assert.Equal(t, 202, resp.StatusCode()) - - <-received1 // request - <-received2 // request - - <-received1 // reply - val1 := validateReceivedMessages(ts, ts.client1, fftypes.MessageTypePrivate, fftypes.TransactionTypeBatchPin, 2, 0) - assert.Equal(t, float64(200), val1.JSONObject()["status"]) - decoded1, err := base64.StdEncoding.DecodeString(val1.JSONObject().GetString("body")) - assert.NoError(t, err) - assert.Regexp(t, "Example YAML", string(decoded1)) - - <-received2 // reply - val2 := validateReceivedMessages(ts, ts.client1, fftypes.MessageTypePrivate, fftypes.TransactionTypeBatchPin, 2, 0) - assert.Equal(t, float64(200), val2.JSONObject()["status"]) - decoded2, err := base64.StdEncoding.DecodeString(val2.JSONObject().GetString("body")) - assert.NoError(t, err) - assert.Regexp(t, "Example YAML", string(decoded2)) -} - -func TestE2EWebhookRequestReplyNoTx(t *testing.T) { - - ts := beforeE2ETest(t) - defer ts.done() - - subJSON := `{ - "transport": "webhooks", - "namespace": "default", - "name": "myhook", - "options": { - "withData": true, - "url": "https://github.com/hyperledger/firefly/raw/main/resources/ff-logo-32.png", - "reply": true, - "replytag": "myreply", - "replytx": "none", - "method": "GET" - }, - "filter": { - "tag": "myrequest" - } - }` - CleanupExistingSubscription(t, ts.client2, "default", "myhook") - sub := CreateSubscription(t, ts.client2, subJSON, 201) - assert.NotNil(t, sub.ID) - - data := fftypes.DataRefOrValue{ - Value: fftypes.Byteable(`{}`), - } - - reply := RequestReply(t, ts.client1, &data, []string{ - ts.org1.Name, - ts.org2.Name, - }, "myrequest", fftypes.TransactionTypeNone) - assert.NotNil(t, reply) - - bodyData := reply.InlineData[0].Value.JSONObject().GetString("body") - b, err := base64.StdEncoding.DecodeString(bodyData) - assert.NoError(t, err) - ffImg, err := png.Decode(bytes.NewReader(b)) - assert.NoError(t, err) - - // Verify we got the right data back by parsing it - convertOptions := image2ascii.DefaultOptions - convertOptions.FixedWidth = 100 - convertOptions.FixedHeight = 60 - convertOptions.Colored = false - converter := image2ascii.NewImageConverter() - str := converter.Image2ASCIIString(ffImg, &convertOptions) - for _, s := range strings.Split(str, "\n") { - if len(strings.TrimSpace(s)) > 0 { - fmt.Println(s) - } - } - -} diff --git a/test/e2e/ethereum_test.go b/test/e2e/ethereum_test.go new file mode 100644 index 0000000000..66e037b267 --- /dev/null +++ b/test/e2e/ethereum_test.go @@ -0,0 +1,28 @@ +// Copyright © 2021 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package e2e + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +func TestEthereumE2ESuite(t *testing.T) { + suite.Run(t, new(OnChainOffChainTestSuite)) + suite.Run(t, new(TokensTestSuite)) +} diff --git a/test/e2e/fabric_test.go b/test/e2e/fabric_test.go new file mode 100644 index 0000000000..4082650027 --- /dev/null +++ b/test/e2e/fabric_test.go @@ -0,0 +1,27 @@ +// Copyright © 2021 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package e2e + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +func TestFabricE2ESuite(t *testing.T) { + suite.Run(t, new(OnChainOffChainTestSuite)) +} diff --git a/test/e2e/onchain_offchain_test.go b/test/e2e/onchain_offchain_test.go new file mode 100644 index 0000000000..ce66b1d6e8 --- /dev/null +++ b/test/e2e/onchain_offchain_test.go @@ -0,0 +1,350 @@ +// Copyright © 2021 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package e2e + +import ( + "bytes" + "crypto/rand" + "encoding/base64" + "fmt" + "image/png" + "math/big" + "strings" + + "github.com/go-resty/resty/v2" + "github.com/hyperledger/firefly/pkg/fftypes" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + image2ascii "github.com/qeesung/image2ascii/convert" +) + +type OnChainOffChainTestSuite struct { + suite.Suite + testState *testState +} + +func (suite *OnChainOffChainTestSuite) BeforeTest(suiteName, testName string) { + suite.testState = beforeE2ETest(suite.T()) +} + +func (suite *OnChainOffChainTestSuite) TestE2EBroadcast() { + defer suite.testState.done() + + received1, changes1 := wsReader(suite.T(), suite.testState.ws1) + received2, changes2 := wsReader(suite.T(), suite.testState.ws2) + + var resp *resty.Response + value := fftypes.Byteable(`"Hello"`) + data := fftypes.DataRefOrValue{ + Value: value, + } + + resp, err := BroadcastMessage(suite.testState.client1, &data, false) + require.NoError(suite.T(), err) + assert.Equal(suite.T(), 202, resp.StatusCode()) + + <-received1 + <-changes1 // also expect database change events + val1 := validateReceivedMessages(suite.testState, suite.testState.client1, fftypes.MessageTypeBroadcast, fftypes.TransactionTypeBatchPin, 1, 0) + assert.Equal(suite.T(), data.Value, val1) + + <-received2 + <-changes2 // also expect database change events + val2 := validateReceivedMessages(suite.testState, suite.testState.client2, fftypes.MessageTypeBroadcast, fftypes.TransactionTypeBatchPin, 1, 0) + assert.Equal(suite.T(), data.Value, val2) + +} + +func (suite *OnChainOffChainTestSuite) TestStrongDatatypesBroadcast() { + defer suite.testState.done() + + var resp *resty.Response + value := fftypes.Byteable(`"Hello"`) + randVer, _ := rand.Int(rand.Reader, big.NewInt(100000000)) + version := fmt.Sprintf("0.0.%d", randVer.Int64()) + data := fftypes.DataRefOrValue{ + Value: value, + Datatype: &fftypes.DatatypeRef{ + Name: "widget", + Version: version, + }, + } + + // Should be rejected as datatype not known + resp, err := BroadcastMessage(suite.testState.client1, &data, true) + require.NoError(suite.T(), err) + assert.Equal(suite.T(), 400, resp.StatusCode()) + assert.Contains(suite.T(), resp.String(), "FF10195") // datatype not found + + dt := &fftypes.Datatype{ + Name: "widget", + Version: version, + Value: widgetSchemaJSON, + } + dt = CreateDatatype(suite.T(), suite.testState.client1, dt, true) + + resp, err = BroadcastMessage(suite.testState.client1, &data, true) + require.NoError(suite.T(), err) + assert.Equal(suite.T(), 400, resp.StatusCode()) + assert.Contains(suite.T(), resp.String(), "FF10198") // does not conform + + data.Value = fftypes.Byteable(`{ + "id": "widget12345", + "name": "mywidget" + }`) + + resp, err = BroadcastMessage(suite.testState.client1, &data, true) + require.NoError(suite.T(), err) + assert.Equal(suite.T(), 200, resp.StatusCode()) + +} + +func (suite *OnChainOffChainTestSuite) TestStrongDatatypesPrivate() { + defer suite.testState.done() + + var resp *resty.Response + value := fftypes.Byteable(`{"foo":"bar"}`) + randVer, _ := rand.Int(rand.Reader, big.NewInt(100000000)) + version := fmt.Sprintf("0.0.%d", randVer.Int64()) + data := fftypes.DataRefOrValue{ + Value: value, + Datatype: &fftypes.DatatypeRef{ + Name: "widget", + Version: version, + }, + } + + // Should be rejected as datatype not known + resp, err := PrivateMessage(suite.T(), suite.testState.client1, &data, []string{ + suite.testState.org1.Name, + suite.testState.org2.Name, + }, "", fftypes.TransactionTypeBatchPin, true) + require.NoError(suite.T(), err) + assert.Equal(suite.T(), 400, resp.StatusCode()) + assert.Contains(suite.T(), resp.String(), "FF10195") // datatype not found + + dt := &fftypes.Datatype{ + Name: "widget", + Version: version, + Value: widgetSchemaJSON, + } + dt = CreateDatatype(suite.T(), suite.testState.client1, dt, true) + + resp, err = PrivateMessage(suite.T(), suite.testState.client1, &data, []string{ + suite.testState.org1.Name, + suite.testState.org2.Name, + }, "", fftypes.TransactionTypeBatchPin, false) + require.NoError(suite.T(), err) + assert.Equal(suite.T(), 400, resp.StatusCode()) + assert.Contains(suite.T(), resp.String(), "FF10198") // does not conform + + data.Value = fftypes.Byteable(`{ + "id": "widget12345", + "name": "mywidget" + }`) + + resp, err = PrivateMessage(suite.T(), suite.testState.client1, &data, []string{ + suite.testState.org1.Name, + suite.testState.org2.Name, + }, "", fftypes.TransactionTypeBatchPin, true) + require.NoError(suite.T(), err) + assert.Equal(suite.T(), 200, resp.StatusCode()) + +} + +func (suite *OnChainOffChainTestSuite) TestE2EPrivate() { + defer suite.testState.done() + + received1, _ := wsReader(suite.T(), suite.testState.ws1) + received2, _ := wsReader(suite.T(), suite.testState.ws2) + + var resp *resty.Response + value := fftypes.Byteable(`"Hello"`) + data := fftypes.DataRefOrValue{ + Value: value, + } + + resp, err := PrivateMessage(suite.T(), suite.testState.client1, &data, []string{ + suite.testState.org1.Name, + suite.testState.org2.Name, + }, "", fftypes.TransactionTypeBatchPin, false) + require.NoError(suite.T(), err) + assert.Equal(suite.T(), 202, resp.StatusCode()) + + <-received1 + val1 := validateReceivedMessages(suite.testState, suite.testState.client1, fftypes.MessageTypePrivate, fftypes.TransactionTypeBatchPin, 1, 0) + assert.Equal(suite.T(), data.Value, val1) + + <-received2 + val2 := validateReceivedMessages(suite.testState, suite.testState.client2, fftypes.MessageTypePrivate, fftypes.TransactionTypeBatchPin, 1, 0) + assert.Equal(suite.T(), data.Value, val2) +} + +func (suite *OnChainOffChainTestSuite) TestE2EBroadcastBlob() { + defer suite.testState.done() + + received1, _ := wsReader(suite.T(), suite.testState.ws1) + received2, _ := wsReader(suite.T(), suite.testState.ws2) + + var resp *resty.Response + + resp, err := BroadcastBlobMessage(suite.T(), suite.testState.client1) + require.NoError(suite.T(), err) + assert.Equal(suite.T(), 202, resp.StatusCode()) + + <-received1 + val1 := validateReceivedMessages(suite.testState, suite.testState.client1, fftypes.MessageTypeBroadcast, fftypes.TransactionTypeBatchPin, 1, 0) + assert.Regexp(suite.T(), "myfile.txt", string(val1)) + + <-received2 + val2 := validateReceivedMessages(suite.testState, suite.testState.client2, fftypes.MessageTypeBroadcast, fftypes.TransactionTypeBatchPin, 1, 0) + assert.Regexp(suite.T(), "myfile.txt", string(val2)) + +} + +func (suite *OnChainOffChainTestSuite) TestE2EPrivateBlobDatatypeTagged() { + defer suite.testState.done() + + received1, _ := wsReader(suite.T(), suite.testState.ws1) + received2, _ := wsReader(suite.T(), suite.testState.ws2) + + var resp *resty.Response + + resp, err := PrivateBlobMessageDatatypeTagged(suite.T(), suite.testState.client1, []string{ + suite.testState.org1.Name, + suite.testState.org2.Name, + }) + require.NoError(suite.T(), err) + assert.Equal(suite.T(), 202, resp.StatusCode()) + + <-received1 + _ = validateReceivedMessages(suite.testState, suite.testState.client1, fftypes.MessageTypePrivate, fftypes.TransactionTypeBatchPin, 1, 0) + + <-received2 + _ = validateReceivedMessages(suite.testState, suite.testState.client2, fftypes.MessageTypePrivate, fftypes.TransactionTypeBatchPin, 1, 0) +} + +func (suite *OnChainOffChainTestSuite) TestE2EWebhookExchange() { + defer suite.testState.done() + + received1, _ := wsReader(suite.T(), suite.testState.ws1) + received2, _ := wsReader(suite.T(), suite.testState.ws2) + + subJSON := `{ + "transport": "webhooks", + "namespace": "default", + "name": "myhook", + "options": { + "withData": true, + "url": "https://raw.githubusercontent.com/hyperledger/firefly/main/test/data/config/firefly.core.yaml", + "reply": true, + "replytag": "myreply", + "method": "GET" + }, + "filter": { + "tag": "myrequest" + } + }` + CleanupExistingSubscription(suite.T(), suite.testState.client2, "default", "myhook") + sub := CreateSubscription(suite.T(), suite.testState.client2, subJSON, 201) + assert.NotNil(suite.T(), sub.ID) + + data := fftypes.DataRefOrValue{ + Value: fftypes.Byteable(`{}`), + } + + var resp *resty.Response + resp, err := PrivateMessage(suite.T(), suite.testState.client1, &data, []string{ + suite.testState.org1.Name, + suite.testState.org2.Name, + }, "myrequest", fftypes.TransactionTypeBatchPin, false) + require.NoError(suite.T(), err) + assert.Equal(suite.T(), 202, resp.StatusCode()) + + <-received1 // request + <-received2 // request + + <-received1 // reply + val1 := validateReceivedMessages(suite.testState, suite.testState.client1, fftypes.MessageTypePrivate, fftypes.TransactionTypeBatchPin, 2, 0) + assert.Equal(suite.T(), float64(200), val1.JSONObject()["status"]) + decoded1, err := base64.StdEncoding.DecodeString(val1.JSONObject().GetString("body")) + assert.NoError(suite.T(), err) + assert.Regexp(suite.T(), "Example YAML", string(decoded1)) + + <-received2 // reply + val2 := validateReceivedMessages(suite.testState, suite.testState.client1, fftypes.MessageTypePrivate, fftypes.TransactionTypeBatchPin, 2, 0) + assert.Equal(suite.T(), float64(200), val2.JSONObject()["status"]) + decoded2, err := base64.StdEncoding.DecodeString(val2.JSONObject().GetString("body")) + assert.NoError(suite.T(), err) + assert.Regexp(suite.T(), "Example YAML", string(decoded2)) +} + +func (suite *OnChainOffChainTestSuite) TestE2EWebhookRequestReplyNoTx() { + defer suite.testState.done() + + subJSON := `{ + "transport": "webhooks", + "namespace": "default", + "name": "myhook", + "options": { + "withData": true, + "url": "https://github.com/hyperledger/firefly/raw/main/resources/ff-logo-32.png", + "reply": true, + "replytag": "myreply", + "replytx": "none", + "method": "GET" + }, + "filter": { + "tag": "myrequest" + } + }` + CleanupExistingSubscription(suite.T(), suite.testState.client2, "default", "myhook") + sub := CreateSubscription(suite.T(), suite.testState.client2, subJSON, 201) + assert.NotNil(suite.T(), sub.ID) + + data := fftypes.DataRefOrValue{ + Value: fftypes.Byteable(`{}`), + } + + reply := RequestReply(suite.T(), suite.testState.client1, &data, []string{ + suite.testState.org1.Name, + suite.testState.org2.Name, + }, "myrequest", fftypes.TransactionTypeNone) + assert.NotNil(suite.T(), reply) + + bodyData := reply.InlineData[0].Value.JSONObject().GetString("body") + b, err := base64.StdEncoding.DecodeString(bodyData) + assert.NoError(suite.T(), err) + ffImg, err := png.Decode(bytes.NewReader(b)) + assert.NoError(suite.T(), err) + + // Verify we got the right data back by parsing it + convertOptions := image2ascii.DefaultOptions + convertOptions.FixedWidth = 100 + convertOptions.FixedHeight = 60 + convertOptions.Colored = false + converter := image2ascii.NewImageConverter() + str := converter.Image2ASCIIString(ffImg, &convertOptions) + for _, s := range strings.Split(str, "\n") { + if len(strings.TrimSpace(s)) > 0 { + fmt.Println(s) + } + } + +} diff --git a/test/e2e/run.sh b/test/e2e/run.sh index 5966d05ee9..f77ff457af 100755 --- a/test/e2e/run.sh +++ b/test/e2e/run.sh @@ -41,6 +41,19 @@ if [ -z "${STACK_FILE}" ]; then STACK_FILE=$STACK_DIR/$STACK_NAME/stack.json fi + +if [ -z "${BLOCKCHAIN_PROVIDER}" ]; then + BLOCKCHAIN_PROVIDER=geth +fi + +if [ -z "${TOKENS_PROVIDER}" ]; then + TOKENS_PROVIDER=erc1155 +fi + +if [ -z "${TEST_SUITE}" ]; then + TEST_SUITE=TestEthereumE2ESuite +fi + cd $CWD if [ "$CREATE_STACK" == "true" ]; then @@ -54,12 +67,12 @@ if [ "$BUILD_FIREFLY" == "true" ]; then fi if [ "$DOWNLOAD_CLI" == "true" ]; then - go install github.com/hyperledger/firefly-cli/ff@v0.0.35 + go install github.com/hyperledger/firefly-cli/ff@v0.0.36 checkOk $? fi if [ "$CREATE_STACK" == "true" ]; then - $CLI init --database $DATABASE_TYPE $STACK_NAME 2 --manifest ../../manifest.json + $CLI init --database $DATABASE_TYPE $STACK_NAME 2 --blockchain-provider $BLOCKCHAIN_PROVIDER --tokens-provider $TOKENS_PROVIDER --manifest ../../manifest.json checkOk $? $CLI pull $STACK_NAME -r 3 @@ -74,5 +87,5 @@ checkOk $? export STACK_FILE -go clean -testcache && go test -v . +go clean -testcache && go test -v . -run $TEST_SUITE checkOk $? diff --git a/test/e2e/tokens_test.go b/test/e2e/tokens_test.go new file mode 100644 index 0000000000..c345c72c3d --- /dev/null +++ b/test/e2e/tokens_test.go @@ -0,0 +1,282 @@ +// Copyright © 2021 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package e2e + +import ( + "fmt" + "time" + + "github.com/hyperledger/firefly/pkg/fftypes" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type TokensTestSuite struct { + suite.Suite + testState *testState +} + +func (suite *TokensTestSuite) BeforeTest(suiteName, testName string) { + suite.testState = beforeE2ETest(suite.T()) +} + +func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { + defer suite.testState.done() + + received1, _ := wsReader(suite.T(), suite.testState.ws1) + received2, _ := wsReader(suite.T(), suite.testState.ws2) + + pools := GetTokenPools(suite.T(), suite.testState.client1, time.Unix(0, 0)) + poolName := fmt.Sprintf("pool%d", len(pools)) + suite.T().Logf("Pool name: %s", poolName) + + pool := &fftypes.TokenPool{ + Name: poolName, + Type: fftypes.TokenTypeFungible, + } + CreateTokenPool(suite.T(), suite.testState.client1, pool, false) + + <-received1 + pools = GetTokenPools(suite.T(), suite.testState.client1, suite.testState.startTime) + assert.Equal(suite.T(), 1, len(pools)) + assert.Equal(suite.T(), "default", pools[0].Namespace) + assert.Equal(suite.T(), "erc1155", pools[0].Connector) + assert.Equal(suite.T(), poolName, pools[0].Name) + assert.Equal(suite.T(), fftypes.TokenTypeFungible, pools[0].Type) + assert.NotEmpty(suite.T(), pools[0].ProtocolID) + + <-received2 + pools = GetTokenPools(suite.T(), suite.testState.client1, suite.testState.startTime) + assert.Equal(suite.T(), 1, len(pools)) + assert.Equal(suite.T(), "default", pools[0].Namespace) + assert.Equal(suite.T(), "erc1155", pools[0].Connector) + assert.Equal(suite.T(), poolName, pools[0].Name) + assert.Equal(suite.T(), fftypes.TokenTypeFungible, pools[0].Type) + assert.NotEmpty(suite.T(), pools[0].ProtocolID) + + transfer := &fftypes.TokenTransferInput{} + transfer.Amount.Int().SetInt64(1) + MintTokens(suite.T(), suite.testState.client1, poolName, transfer, false) + + <-received1 + transfers := GetTokenTransfers(suite.T(), suite.testState.client1, poolName) + assert.Equal(suite.T(), 1, len(transfers)) + assert.Equal(suite.T(), "erc1155", transfers[0].Connector) + assert.Equal(suite.T(), fftypes.TokenTransferTypeMint, transfers[0].Type) + assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) + validateAccountBalances(suite.T(), suite.testState.client1, poolName, "", map[string]int64{ + suite.testState.org1.Identity: 1, + }) + + <-received2 + transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolName) + assert.Equal(suite.T(), 1, len(transfers)) + assert.Equal(suite.T(), "erc1155", transfers[0].Connector) + assert.Equal(suite.T(), fftypes.TokenTransferTypeMint, transfers[0].Type) + assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) + validateAccountBalances(suite.T(), suite.testState.client2, poolName, "", map[string]int64{ + suite.testState.org1.Identity: 1, + }) + + transfer = &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + To: suite.testState.org2.Identity, + }, + Message: &fftypes.MessageInOut{ + InlineData: fftypes.InlineData{ + { + Value: fftypes.Byteable(`"payment for data"`), + }, + }, + }, + } + transfer.Amount.Int().SetInt64(1) + TransferTokens(suite.T(), suite.testState.client1, poolName, transfer, false) + + <-received1 // one event for transfer + <-received1 // one event for message + transfers = GetTokenTransfers(suite.T(), suite.testState.client1, poolName) + assert.Equal(suite.T(), 2, len(transfers)) + assert.Equal(suite.T(), "erc1155", transfers[0].Connector) + assert.Equal(suite.T(), fftypes.TokenTransferTypeTransfer, transfers[0].Type) + assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) + data := GetDataForMessage(suite.T(), suite.testState.client1, suite.testState.startTime, transfers[0].MessageHash) + assert.Equal(suite.T(), 1, len(data)) + assert.Equal(suite.T(), `"payment for data"`, data[0].Value.String()) + validateAccountBalances(suite.T(), suite.testState.client1, poolName, "", map[string]int64{ + suite.testState.org1.Identity: 0, + suite.testState.org2.Identity: 1, + }) + + <-received2 // one event for transfer + <-received2 // one event for message + transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolName) + assert.Equal(suite.T(), 2, len(transfers)) + assert.Equal(suite.T(), "erc1155", transfers[0].Connector) + assert.Equal(suite.T(), fftypes.TokenTransferTypeTransfer, transfers[0].Type) + assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) + validateAccountBalances(suite.T(), suite.testState.client2, poolName, "", map[string]int64{ + suite.testState.org1.Identity: 0, + suite.testState.org2.Identity: 1, + }) + + transfer = &fftypes.TokenTransferInput{} + transfer.Amount.Int().SetInt64(1) + BurnTokens(suite.T(), suite.testState.client2, poolName, transfer, false) + + <-received2 + transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolName) + assert.Equal(suite.T(), 3, len(transfers)) + assert.Equal(suite.T(), "erc1155", transfers[0].Connector) + assert.Equal(suite.T(), fftypes.TokenTransferTypeBurn, transfers[0].Type) + assert.Equal(suite.T(), "", transfers[0].TokenIndex) + assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) + validateAccountBalances(suite.T(), suite.testState.client2, poolName, "", map[string]int64{ + suite.testState.org1.Identity: 0, + suite.testState.org2.Identity: 0, + }) + + <-received1 + transfers = GetTokenTransfers(suite.T(), suite.testState.client1, poolName) + assert.Equal(suite.T(), 3, len(transfers)) + assert.Equal(suite.T(), "erc1155", transfers[0].Connector) + assert.Equal(suite.T(), fftypes.TokenTransferTypeBurn, transfers[0].Type) + assert.Equal(suite.T(), "", transfers[0].TokenIndex) + assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) + validateAccountBalances(suite.T(), suite.testState.client1, poolName, "", map[string]int64{ + suite.testState.org1.Identity: 0, + suite.testState.org2.Identity: 0, + }) +} + +func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() { + defer suite.testState.done() + + received1, _ := wsReader(suite.T(), suite.testState.ws1) + received2, _ := wsReader(suite.T(), suite.testState.ws2) + + pools := GetTokenPools(suite.T(), suite.testState.client1, time.Unix(0, 0)) + poolName := fmt.Sprintf("pool%d", len(pools)) + suite.T().Logf("Pool name: %s", poolName) + + pool := &fftypes.TokenPool{ + Name: poolName, + Type: fftypes.TokenTypeNonFungible, + } + poolOut := CreateTokenPool(suite.T(), suite.testState.client1, pool, true) + assert.Equal(suite.T(), "default", poolOut.Namespace) + assert.Equal(suite.T(), poolName, poolOut.Name) + assert.Equal(suite.T(), fftypes.TokenTypeNonFungible, poolOut.Type) + assert.NotEmpty(suite.T(), poolOut.ProtocolID) + + <-received1 + <-received2 + pools = GetTokenPools(suite.T(), suite.testState.client1, suite.testState.startTime) + assert.Equal(suite.T(), 1, len(pools)) + assert.Equal(suite.T(), "default", pools[0].Namespace) + assert.Equal(suite.T(), poolName, pools[0].Name) + assert.Equal(suite.T(), fftypes.TokenTypeNonFungible, pools[0].Type) + assert.NotEmpty(suite.T(), pools[0].ProtocolID) + + transfer := &fftypes.TokenTransferInput{} + transfer.Amount.Int().SetInt64(1) + transferOut := MintTokens(suite.T(), suite.testState.client1, poolName, transfer, true) + assert.Equal(suite.T(), fftypes.TokenTransferTypeMint, transferOut.Type) + assert.Equal(suite.T(), "1", transferOut.TokenIndex) + assert.Equal(suite.T(), int64(1), transferOut.Amount.Int().Int64()) + validateAccountBalances(suite.T(), suite.testState.client1, poolName, "1", map[string]int64{ + suite.testState.org1.Identity: 1, + }) + + <-received1 + <-received2 + transfers := GetTokenTransfers(suite.T(), suite.testState.client2, poolName) + assert.Equal(suite.T(), 1, len(transfers)) + assert.Equal(suite.T(), fftypes.TokenTransferTypeMint, transfers[0].Type) + assert.Equal(suite.T(), "1", transfers[0].TokenIndex) + assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) + validateAccountBalances(suite.T(), suite.testState.client2, poolName, "1", map[string]int64{ + suite.testState.org1.Identity: 1, + }) + + transfer = &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + TokenIndex: "1", + To: suite.testState.org2.Identity, + }, + Message: &fftypes.MessageInOut{ + InlineData: fftypes.InlineData{ + { + Value: fftypes.Byteable(`"ownership change"`), + }, + }, + }, + } + transfer.Amount.Int().SetInt64(1) + transferOut = TransferTokens(suite.T(), suite.testState.client1, poolName, transfer, true) + assert.Equal(suite.T(), fftypes.TokenTransferTypeTransfer, transferOut.Type) + assert.Equal(suite.T(), "1", transferOut.TokenIndex) + assert.Equal(suite.T(), int64(1), transferOut.Amount.Int().Int64()) + data := GetDataForMessage(suite.T(), suite.testState.client1, suite.testState.startTime, transferOut.MessageHash) + assert.Equal(suite.T(), 1, len(data)) + assert.Equal(suite.T(), `"ownership change"`, data[0].Value.String()) + validateAccountBalances(suite.T(), suite.testState.client1, poolName, "1", map[string]int64{ + suite.testState.org1.Identity: 0, + suite.testState.org2.Identity: 1, + }) + + <-received1 // one event for transfer + <-received1 // one event for message + <-received2 // one event for transfer + <-received2 // one event for message + transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolName) + assert.Equal(suite.T(), 2, len(transfers)) + assert.Equal(suite.T(), fftypes.TokenTransferTypeTransfer, transfers[0].Type) + assert.Equal(suite.T(), "1", transfers[0].TokenIndex) + assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) + validateAccountBalances(suite.T(), suite.testState.client2, poolName, "1", map[string]int64{ + suite.testState.org1.Identity: 0, + suite.testState.org2.Identity: 1, + }) + + transfer = &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + TokenIndex: "1", + }, + } + transfer.Amount.Int().SetInt64(1) + transferOut = BurnTokens(suite.T(), suite.testState.client2, poolName, transfer, true) + assert.Equal(suite.T(), fftypes.TokenTransferTypeBurn, transferOut.Type) + assert.Equal(suite.T(), "1", transferOut.TokenIndex) + assert.Equal(suite.T(), int64(1), transferOut.Amount.Int().Int64()) + validateAccountBalances(suite.T(), suite.testState.client2, poolName, "1", map[string]int64{ + suite.testState.org1.Identity: 0, + suite.testState.org2.Identity: 0, + }) + + <-received2 + <-received1 + transfers = GetTokenTransfers(suite.T(), suite.testState.client1, poolName) + assert.Equal(suite.T(), 3, len(transfers)) + assert.Equal(suite.T(), fftypes.TokenTransferTypeBurn, transfers[0].Type) + assert.Equal(suite.T(), "1", transfers[0].TokenIndex) + assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) + validateAccountBalances(suite.T(), suite.testState.client1, poolName, "1", map[string]int64{ + suite.testState.org1.Identity: 0, + suite.testState.org2.Identity: 0, + }) +}