diff --git a/db/db.go b/db/db.go index 20683157..c4e4bed8 100644 --- a/db/db.go +++ b/db/db.go @@ -374,6 +374,9 @@ func buildBatchKeysInsertQuery(bks []types.BatchKey) (string, []interface{}) { func buildOffchainDataInsertQuery(ods []types.OffChainData) (string, []interface{}) { const columnsAffected = 3 + // Remove duplicates from the given offchain data + ods = types.RemoveDuplicateOffChainData(ods) + args := make([]interface{}, len(ods)*columnsAffected) values := make([]string, len(ods)) for i, od := range ods { diff --git a/db/db_test.go b/db/db_test.go index 8087dc54..4f268884 100644 --- a/db/db_test.go +++ b/db/db_test.go @@ -175,7 +175,7 @@ func Test_DB_StoreUnresolvedBatchKeys(t *testing.T) { name: "one value inserted", bk: []types.BatchKey{{ Number: 1, - Hash: common.HexToHash("key1"), + Hash: common.BytesToHash([]byte("key1")), }}, expectedQuery: `INSERT INTO data_node.unresolved_batches (num, hash) VALUES ($1, $2) ON CONFLICT (num, hash) DO NOTHING`, }, @@ -183,10 +183,10 @@ func Test_DB_StoreUnresolvedBatchKeys(t *testing.T) { name: "several values inserted", bk: []types.BatchKey{{ Number: 1, - Hash: common.HexToHash("key1"), + Hash: common.BytesToHash([]byte("key1")), }, { Number: 2, - Hash: common.HexToHash("key2"), + Hash: common.BytesToHash([]byte("key2")), }}, expectedQuery: `INSERT INTO data_node.unresolved_batches (num, hash) VALUES ($1, $2),($3, $4) ON CONFLICT (num, hash) DO NOTHING`, }, @@ -194,7 +194,7 @@ func Test_DB_StoreUnresolvedBatchKeys(t *testing.T) { name: "error returned", bk: []types.BatchKey{{ Number: 1, - Hash: common.HexToHash("key1"), + Hash: common.BytesToHash([]byte("key1")), }}, expectedQuery: `INSERT INTO data_node.unresolved_batches (num, hash) VALUES ($1, $2) ON CONFLICT (num, hash) DO NOTHING`, returnErr: errors.New("test error"), @@ -262,14 +262,14 @@ func Test_DB_GetUnresolvedBatchKeys(t *testing.T) { name: "successfully selected data", bks: []types.BatchKey{{ Number: 1, - Hash: common.HexToHash("key1"), + Hash: common.BytesToHash([]byte("key1")), }}, }, { name: "error returned", bks: []types.BatchKey{{ Number: 1, - Hash: common.HexToHash("key1"), + Hash: common.BytesToHash([]byte("key1")), }}, returnErr: errors.New("test error"), }, @@ -332,7 +332,7 @@ func Test_DB_DeleteUnresolvedBatchKeys(t *testing.T) { name: "value deleted", bks: []types.BatchKey{{ Number: 1, - Hash: common.HexToHash("key1"), + Hash: common.BytesToHash([]byte("key1")), }}, expectedQuery: `DELETE FROM data_node.unresolved_batches WHERE (num, hash) IN (($1, $2))`, }, @@ -340,10 +340,10 @@ func Test_DB_DeleteUnresolvedBatchKeys(t *testing.T) { name: "multiple values deleted", bks: []types.BatchKey{{ Number: 1, - Hash: common.HexToHash("key1"), + Hash: common.BytesToHash([]byte("key1")), }, { Number: 2, - Hash: common.HexToHash("key2"), + Hash: common.BytesToHash([]byte("key2")), }}, expectedQuery: `DELETE FROM data_node.unresolved_batches WHERE (num, hash) IN (($1, $2),($3, $4))`, }, @@ -351,7 +351,7 @@ func Test_DB_DeleteUnresolvedBatchKeys(t *testing.T) { name: "error returned", bks: []types.BatchKey{{ Number: 1, - Hash: common.HexToHash("key1"), + Hash: common.BytesToHash([]byte("key1")), }}, expectedQuery: `DELETE FROM data_node.unresolved_batches WHERE (num, hash) IN (($1, $2))`, returnErr: errors.New("test error"), @@ -416,7 +416,7 @@ func Test_DB_StoreOffChainData(t *testing.T) { { name: "one value inserted", ods: []types.OffChainData{{ - Key: common.HexToHash("key1"), + Key: common.BytesToHash([]byte("key1")), Value: []byte("value1"), }}, expectedQuery: `INSERT INTO data_node.offchain_data (key, value, batch_num) VALUES ($1, $2, $3) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, batch_num = EXCLUDED.batch_num`, @@ -424,10 +424,10 @@ func Test_DB_StoreOffChainData(t *testing.T) { { name: "several values inserted", ods: []types.OffChainData{{ - Key: common.HexToHash("key1"), + Key: common.BytesToHash([]byte("key1")), Value: []byte("value1"), }, { - Key: common.HexToHash("key2"), + Key: common.BytesToHash([]byte("key2")), Value: []byte("value2"), }}, expectedQuery: `INSERT INTO data_node.offchain_data (key, value, batch_num) VALUES ($1, $2, $3),($4, $5, $6) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, batch_num = EXCLUDED.batch_num`, @@ -435,7 +435,7 @@ func Test_DB_StoreOffChainData(t *testing.T) { { name: "error returned", ods: []types.OffChainData{{ - Key: common.HexToHash("key1"), + Key: common.BytesToHash([]byte("key1")), Value: []byte("value1"), }}, expectedQuery: `INSERT INTO data_node.offchain_data (key, value, batch_num) VALUES ($1, $2, $3) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, batch_num = EXCLUDED.batch_num`, @@ -499,13 +499,13 @@ func Test_DB_GetOffChainData(t *testing.T) { { name: "successfully selected value", od: []types.OffChainData{{ - Key: common.HexToHash("key1"), + Key: common.BytesToHash([]byte("key1")), Value: []byte("value1"), BatchNum: 1, }}, key: common.BytesToHash([]byte("key1")), expected: &types.OffChainData{ - Key: common.HexToHash("key1"), + Key: common.BytesToHash([]byte("key1")), Value: []byte("value1"), BatchNum: 1, }, @@ -513,7 +513,7 @@ func Test_DB_GetOffChainData(t *testing.T) { { name: "error returned", od: []types.OffChainData{{ - Key: common.HexToHash("key1"), + Key: common.BytesToHash([]byte("key1")), Value: []byte("value1"), }}, key: common.BytesToHash([]byte("key1")), @@ -522,7 +522,7 @@ func Test_DB_GetOffChainData(t *testing.T) { { name: "no rows", od: []types.OffChainData{{ - Key: common.HexToHash("key1"), + Key: common.BytesToHash([]byte("key1")), Value: []byte("value1"), }}, key: common.BytesToHash([]byte("undefined")), @@ -587,7 +587,7 @@ func Test_DB_ListOffChainData(t *testing.T) { { name: "successfully selected one value", od: []types.OffChainData{{ - Key: common.HexToHash("key1"), + Key: common.BytesToHash([]byte("key1")), Value: []byte("value1"), }}, keys: []common.Hash{ @@ -605,11 +605,11 @@ func Test_DB_ListOffChainData(t *testing.T) { { name: "successfully selected two values", od: []types.OffChainData{{ - Key: common.HexToHash("key1"), + Key: common.BytesToHash([]byte("key1")), Value: []byte("value1"), BatchNum: 1, }, { - Key: common.HexToHash("key2"), + Key: common.BytesToHash([]byte("key2")), Value: []byte("value2"), BatchNum: 2, }}, @@ -634,7 +634,7 @@ func Test_DB_ListOffChainData(t *testing.T) { { name: "error returned", od: []types.OffChainData{{ - Key: common.HexToHash("key1"), + Key: common.BytesToHash([]byte("key1")), Value: []byte("value1"), }}, keys: []common.Hash{ @@ -646,7 +646,7 @@ func Test_DB_ListOffChainData(t *testing.T) { { name: "no rows", od: []types.OffChainData{{ - Key: common.HexToHash("key1"), + Key: common.BytesToHash([]byte("key1")), Value: []byte("value1"), }}, keys: []common.Hash{ @@ -722,10 +722,10 @@ func Test_DB_CountOffchainData(t *testing.T) { { name: "two values found", od: []types.OffChainData{{ - Key: common.HexToHash("key1"), + Key: common.BytesToHash([]byte("key1")), Value: []byte("value1"), }, { - Key: common.HexToHash("key1"), + Key: common.BytesToHash([]byte("key1")), Value: []byte("value2"), }}, count: 2, @@ -737,7 +737,7 @@ func Test_DB_CountOffchainData(t *testing.T) { { name: "error returned", od: []types.OffChainData{{ - Key: common.HexToHash("key1"), + Key: common.BytesToHash([]byte("key1")), Value: []byte("value1"), }}, returnErr: errors.New("test error"), @@ -797,11 +797,11 @@ func Test_DB_DetectOffchainDataGaps(t *testing.T) { { name: "one gap found", seed: []types.OffChainData{{ - Key: common.HexToHash("key1"), + Key: common.BytesToHash([]byte("key1")), Value: []byte("value1"), BatchNum: 1, }, { - Key: common.HexToHash("key2"), + Key: common.BytesToHash([]byte("key2")), Value: []byte("value2"), BatchNum: 2, }, { @@ -816,7 +816,7 @@ func Test_DB_DetectOffchainDataGaps(t *testing.T) { { name: "error returned", seed: []types.OffChainData{{ - Key: common.HexToHash("key1"), + Key: common.BytesToHash([]byte("key1")), Value: []byte("value1"), }}, returnErr: errors.New("test error"), diff --git a/pkg/backoff/backoff.go b/pkg/backoff/backoff.go index 9bae5338..9476a2a9 100644 --- a/pkg/backoff/backoff.go +++ b/pkg/backoff/backoff.go @@ -3,9 +3,9 @@ package backoff import "time" // Exponential performs exponential backoff attempts on a given action -func Exponential(action func() error, max uint, wait time.Duration) error { +func Exponential(action func() error, attempts uint, wait time.Duration) error { var err error - for i := uint(0); i < max; i++ { + for i := uint(0); i < attempts; i++ { if err = action(); err == nil { return nil } diff --git a/services/datacom/datacom_test.go b/services/datacom/datacom_test.go index dec8b5a0..8261f431 100644 --- a/services/datacom/datacom_test.go +++ b/services/datacom/datacom_test.go @@ -25,14 +25,10 @@ func TestDataCom_SignSequence(t *testing.T) { storeOffChainDataReturns []interface{} sender *ecdsa.PrivateKey signer *ecdsa.PrivateKey + sequence types.Sequence expectedError string } - sequence := types.Sequence{ - types.ArgBytes([]byte{0, 1}), - types.ArgBytes([]byte{2, 3}), - } - privateKey, err := crypto.GenerateKey() require.NoError(t, err) @@ -51,7 +47,7 @@ func TestDataCom_SignSequence(t *testing.T) { dbMock := mocks.NewDB(t) if len(cfg.storeOffChainDataReturns) > 0 { - dbMock.On("StoreOffChainData", mock.Anything, sequence.OffChainData()).Return( + dbMock.On("StoreOffChainData", mock.Anything, cfg.sequence.OffChainData()).Return( cfg.storeOffChainDataReturns...).Once() } @@ -68,15 +64,15 @@ func TestDataCom_SignSequence(t *testing.T) { sqr.Start(context.Background()) if cfg.sender != nil { - signature, err := sequence.Sign(cfg.sender) + signature, err := cfg.sequence.Sign(cfg.sender) require.NoError(t, err) signedSequence = &types.SignedSequence{ - Sequence: sequence, + Sequence: cfg.sequence, Signature: signature, } } else { signedSequence = &types.SignedSequence{ - Sequence: sequence, + Sequence: cfg.sequence, Signature: []byte{}, } } @@ -106,6 +102,10 @@ func TestDataCom_SignSequence(t *testing.T) { testFn(t, testConfig{ expectedError: "failed to verify sender", + sequence: types.Sequence{ + types.ArgBytes{0, 1}, + types.ArgBytes{2, 3}, + }, }) }) @@ -115,6 +115,10 @@ func TestDataCom_SignSequence(t *testing.T) { testFn(t, testConfig{ sender: privateKey, expectedError: "unauthorized", + sequence: types.Sequence{ + types.ArgBytes{0, 1}, + types.ArgBytes{2, 3}, + }, }) }) @@ -124,6 +128,10 @@ func TestDataCom_SignSequence(t *testing.T) { testFn(t, testConfig{ sender: privateKey, expectedError: "unauthorized", + sequence: types.Sequence{ + types.ArgBytes{0, 1}, + types.ArgBytes{2, 3}, + }, }) }) @@ -134,6 +142,10 @@ func TestDataCom_SignSequence(t *testing.T) { sender: otherPrivateKey, expectedError: "failed to store offchain data", storeOffChainDataReturns: []interface{}{errors.New("error")}, + sequence: types.Sequence{ + types.ArgBytes{0, 1}, + types.ArgBytes{2, 3}, + }, }) }) @@ -150,6 +162,10 @@ func TestDataCom_SignSequence(t *testing.T) { signer: key, storeOffChainDataReturns: []interface{}{nil}, expectedError: "failed to sign", + sequence: types.Sequence{ + types.ArgBytes{0, 1}, + types.ArgBytes{2, 3}, + }, }) }) @@ -159,6 +175,10 @@ func TestDataCom_SignSequence(t *testing.T) { testFn(t, testConfig{ sender: otherPrivateKey, storeOffChainDataReturns: []interface{}{nil}, + sequence: types.Sequence{ + types.ArgBytes{0, 1}, + types.ArgBytes{2, 3}, + }, }) }) } diff --git a/types/types.go b/types/types.go index 146c195d..6d262d7f 100644 --- a/types/types.go +++ b/types/types.go @@ -37,6 +37,19 @@ type OffChainData struct { BatchNum uint64 } +// RemoveDuplicateOffChainData removes duplicate off chain data +func RemoveDuplicateOffChainData(ods []OffChainData) []OffChainData { + seen := make(map[common.Hash]struct{}) + result := []OffChainData{} + for _, od := range ods { + if _, ok := seen[od.Key]; !ok { + seen[od.Key] = struct{}{} + result = append(result, od) + } + } + return result +} + // ArgUint64 helps to marshal uint64 values provided in the RPC requests type ArgUint64 uint64 diff --git a/types/types_test.go b/types/types_test.go index f49bd2ec..c8e3493b 100644 --- a/types/types_test.go +++ b/types/types_test.go @@ -3,6 +3,8 @@ package types import ( "testing" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -39,3 +41,59 @@ func TestIsHexValid(t *testing.T) { }) } } + +func TestRemoveDuplicateOffChainData(t *testing.T) { + type args struct { + ods []OffChainData + } + tests := []struct { + name string + args args + want []OffChainData + }{ + { + name: "no duplicates", + args: args{ + ods: []OffChainData{ + { + Key: common.BytesToHash([]byte("key1")), + }, + { + Key: common.BytesToHash([]byte("key2")), + }, + }, + }, + want: []OffChainData{ + { + Key: common.BytesToHash([]byte("key1")), + }, + { + Key: common.BytesToHash([]byte("key2")), + }, + }, + }, + { + name: "with duplicates", + args: args{ + ods: []OffChainData{ + { + Key: common.BytesToHash([]byte("key1")), + }, + { + Key: common.BytesToHash([]byte("key1")), + }, + }, + }, + want: []OffChainData{ + { + Key: common.BytesToHash([]byte("key1")), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, RemoveDuplicateOffChainData(tt.args.ods), "RemoveDuplicateOffChainData(%v)", tt.args.ods) + }) + } +}