diff --git a/Makefile b/Makefile index 0768756652..768ed9af95 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,6 @@ $(eval $(call makemock, internal/oapiffi, FFISwaggerGen, oapiffimo $(eval $(call makemock, internal/orchestrator, Orchestrator, orchestratormocks)) $(eval $(call makemock, internal/apiserver, Server, apiservermocks)) $(eval $(call makemock, internal/apiserver, IServer, apiservermocks)) -$(eval $(call makemock, internal/txcommon, Helper, txcommonmocks)) firefly-nocgo: ${GOFILES} CGO_ENABLED=0 $(VGO) build -o ${BINARY_NAME}-nocgo -ldflags "-X main.buildDate=`date -u +\"%Y-%m-%dT%H:%M:%SZ\"` -X main.buildVersion=$(BUILD_VERSION)" -tags=prod -tags=prod -v @@ -88,4 +87,4 @@ deps: swagger: $(VGO) test ./internal/apiserver -timeout=10s -tags swagger manifest: - ./manifestgen.sh \ No newline at end of file + ./manifestgen.sh diff --git a/db/migrations/postgres/000054_create_blockchainevents_table.down.sql b/db/migrations/postgres/000054_create_blockchainevents_table.down.sql new file mode 100644 index 0000000000..2f11216c86 --- /dev/null +++ b/db/migrations/postgres/000054_create_blockchainevents_table.down.sql @@ -0,0 +1,3 @@ +BEGIN; +DROP TABLE IF EXISTS blockchainevents; +COMMIT; diff --git a/db/migrations/postgres/000054_create_blockchainevents_table.up.sql b/db/migrations/postgres/000054_create_blockchainevents_table.up.sql new file mode 100644 index 0000000000..dd36f7f47a --- /dev/null +++ b/db/migrations/postgres/000054_create_blockchainevents_table.up.sql @@ -0,0 +1,21 @@ +BEGIN; +CREATE TABLE blockchainevents ( + seq INTEGER PRIMARY KEY AUTOINCREMENT, + id UUID NOT NULL, + source VARCHAR(256) NOT NULL, + namespace VARCHAR(64) NOT NULL, + name VARCHAR(256) NOT NULL, + protocol_id VARCHAR(256) NOT NULL, + timestamp BIGINT NOT NULL, + subscription_id UUID, + output BYTEA, + info BYTEA, + tx_type VARCHAR(64), + tx_id UUID +); + +CREATE INDEX blockchainevents_id ON blockchainevents(id); +CREATE INDEX blockchainevents_tx ON blockchainevents(tx_id); +CREATE INDEX blockchainevents_timestamp ON blockchainevents(timestamp); +CREATE INDEX blockchainevents_subscription_id ON blockchainevents(subscription_id); +COMMIT; diff --git a/db/migrations/postgres/000054_create_contractevents_table.down.sql b/db/migrations/postgres/000054_create_contractevents_table.down.sql deleted file mode 100644 index 13abc8804b..0000000000 --- a/db/migrations/postgres/000054_create_contractevents_table.down.sql +++ /dev/null @@ -1,3 +0,0 @@ -BEGIN; -DROP TABLE IF EXISTS contractevents; -COMMIT; \ No newline at end of file diff --git a/db/migrations/postgres/000054_create_contractevents_table.up.sql b/db/migrations/postgres/000054_create_contractevents_table.up.sql deleted file mode 100644 index b1f5b5f972..0000000000 --- a/db/migrations/postgres/000054_create_contractevents_table.up.sql +++ /dev/null @@ -1,15 +0,0 @@ -BEGIN; -CREATE TABLE contractevents ( - seq SERIAL PRIMARY KEY, - id UUID NOT NULL, - namespace VARCHAR(64) NOT NULL, - name VARCHAR(1024) NOT NULL, - subscription_id UUID NOT NULL, - outputs TEXT, - info TEXT, - timestamp BIGINT NOT NULL -); -CREATE INDEX contractevents_name ON contractevents(namespace,name); -CREATE INDEX contractevents_timestamp ON contractevents(timestamp); -CREATE INDEX contractevents_subscription_id ON contractevents(subscription_id); -COMMIT; \ No newline at end of file diff --git a/db/migrations/postgres/000056_drop_transactions_columns.down.sql b/db/migrations/postgres/000056_drop_transactions_columns.down.sql new file mode 100644 index 0000000000..c9208a5f87 --- /dev/null +++ b/db/migrations/postgres/000056_drop_transactions_columns.down.sql @@ -0,0 +1,10 @@ +BEGIN; +ALTER TABLE transactions ADD COLUMN ref UUID; +ALTER TABLE transactions ADD COLUMN signer VARCHAR(1024); +ALTER TABLE transactions ADD COLUMN hash CHAR(64); +ALTER TABLE transactions ADD COLUMN protocol_id VARCHAR(256); +ALTER TABLE transactions ADD COLUMN info BYTEA; + +CREATE INDEX transactions_protocol_id ON transactions(protocol_id); +CREATE INDEX transactions_ref ON transactions(ref); +COMMIT; diff --git a/db/migrations/postgres/000056_drop_transactions_columns.up.sql b/db/migrations/postgres/000056_drop_transactions_columns.up.sql new file mode 100644 index 0000000000..22c8ee5b4f --- /dev/null +++ b/db/migrations/postgres/000056_drop_transactions_columns.up.sql @@ -0,0 +1,10 @@ +BEGIN; +DROP INDEX transactions_protocol_id; +DROP INDEX transactions_ref; + +ALTER TABLE transactions DROP COLUMN ref; +ALTER TABLE transactions DROP COLUMN signer; +ALTER TABLE transactions DROP COLUMN hash; +ALTER TABLE transactions DROP COLUMN protocol_id; +ALTER TABLE transactions DROP COLUMN info; +COMMIT; diff --git a/db/migrations/postgres/000057_add_tokentransfer_blockchainevent.down.sql b/db/migrations/postgres/000057_add_tokentransfer_blockchainevent.down.sql new file mode 100644 index 0000000000..0cf0f00dc2 --- /dev/null +++ b/db/migrations/postgres/000057_add_tokentransfer_blockchainevent.down.sql @@ -0,0 +1,5 @@ +BEGIN; +ALTER TABLE tokentransfer DROP COLUMN blockchain_event; +ALTER TABLE tokentransfer ADD COLUMN tx_type VARCHAR(64); +ALTER TABLE tokentransfer ADD COLUMN tx_id UUID; +COMMIT; diff --git a/db/migrations/postgres/000057_add_tokentransfer_blockchainevent.up.sql b/db/migrations/postgres/000057_add_tokentransfer_blockchainevent.up.sql new file mode 100644 index 0000000000..ea5dbb419c --- /dev/null +++ b/db/migrations/postgres/000057_add_tokentransfer_blockchainevent.up.sql @@ -0,0 +1,5 @@ +BEGIN; +ALTER TABLE tokentransfer DROP COLUMN tx_type; +ALTER TABLE tokentransfer DROP COLUMN tx_id; +ALTER TABLE tokentransfer ADD COLUMN blockchain_event UUID; +COMMIT; diff --git a/db/migrations/sqlite/000054_create_blockchainevents_table.down.sql b/db/migrations/sqlite/000054_create_blockchainevents_table.down.sql new file mode 100644 index 0000000000..13fe5cd11d --- /dev/null +++ b/db/migrations/sqlite/000054_create_blockchainevents_table.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS blockchainevents; diff --git a/db/migrations/sqlite/000054_create_blockchainevents_table.up.sql b/db/migrations/sqlite/000054_create_blockchainevents_table.up.sql new file mode 100644 index 0000000000..cbd1443e5b --- /dev/null +++ b/db/migrations/sqlite/000054_create_blockchainevents_table.up.sql @@ -0,0 +1,19 @@ +CREATE TABLE blockchainevents ( + seq INTEGER PRIMARY KEY AUTOINCREMENT, + id UUID NOT NULL, + source VARCHAR(256) NOT NULL, + namespace VARCHAR(64) NOT NULL, + name VARCHAR(256) NOT NULL, + protocol_id VARCHAR(256) NOT NULL, + timestamp BIGINT NOT NULL, + subscription_id UUID, + output TEXT, + info TEXT, + tx_type VARCHAR(64), + tx_id UUID +); + +CREATE INDEX blockchainevents_id ON blockchainevents(id); +CREATE INDEX blockchainevents_tx ON blockchainevents(tx_id); +CREATE INDEX blockchainevents_timestamp ON blockchainevents(timestamp); +CREATE INDEX blockchainevents_subscription_id ON blockchainevents(subscription_id); diff --git a/db/migrations/sqlite/000054_create_contractevents_table.down.sql b/db/migrations/sqlite/000054_create_contractevents_table.down.sql deleted file mode 100644 index ce95cee443..0000000000 --- a/db/migrations/sqlite/000054_create_contractevents_table.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS contractevents; diff --git a/db/migrations/sqlite/000054_create_contractevents_table.up.sql b/db/migrations/sqlite/000054_create_contractevents_table.up.sql deleted file mode 100644 index f17e0680fa..0000000000 --- a/db/migrations/sqlite/000054_create_contractevents_table.up.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE TABLE contractevents ( - seq INTEGER PRIMARY KEY AUTOINCREMENT, - id UUID NOT NULL, - namespace VARCHAR(64) NOT NULL, - name VARCHAR(1024) NOT NULL, - subscription_id UUID NOT NULL, - outputs TEXT, - info TEXT, - timestamp BIGINT NOT NULL -); - -CREATE INDEX contractevents_name ON contractevents(namespace,name); -CREATE INDEX contractevents_timestamp ON contractevents(timestamp); -CREATE INDEX contractevents_subscription_id ON contractevents(subscription_id); \ No newline at end of file diff --git a/db/migrations/sqlite/000056_drop_transactions_columns.down.sql b/db/migrations/sqlite/000056_drop_transactions_columns.down.sql new file mode 100644 index 0000000000..9712c85847 --- /dev/null +++ b/db/migrations/sqlite/000056_drop_transactions_columns.down.sql @@ -0,0 +1,8 @@ +ALTER TABLE transactions ADD COLUMN ref UUID; +ALTER TABLE transactions ADD COLUMN signer VARCHAR(1024); +ALTER TABLE transactions ADD COLUMN hash CHAR(64); +ALTER TABLE transactions ADD COLUMN protocol_id VARCHAR(256); +ALTER TABLE transactions ADD COLUMN info BYTEA; + +CREATE INDEX transactions_protocol_id ON transactions(protocol_id); +CREATE INDEX transactions_ref ON transactions(ref); diff --git a/db/migrations/sqlite/000056_drop_transactions_columns.up.sql b/db/migrations/sqlite/000056_drop_transactions_columns.up.sql new file mode 100644 index 0000000000..ef85b5eca4 --- /dev/null +++ b/db/migrations/sqlite/000056_drop_transactions_columns.up.sql @@ -0,0 +1,8 @@ +DROP INDEX transactions_protocol_id; +DROP INDEX transactions_ref; + +ALTER TABLE transactions DROP COLUMN ref; +ALTER TABLE transactions DROP COLUMN signer; +ALTER TABLE transactions DROP COLUMN hash; +ALTER TABLE transactions DROP COLUMN protocol_id; +ALTER TABLE transactions DROP COLUMN info; diff --git a/db/migrations/sqlite/000057_add_tokentransfer_blockchainevent.down.sql b/db/migrations/sqlite/000057_add_tokentransfer_blockchainevent.down.sql new file mode 100644 index 0000000000..922f2c7a6d --- /dev/null +++ b/db/migrations/sqlite/000057_add_tokentransfer_blockchainevent.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE tokentransfer DROP COLUMN blockchain_event; +ALTER TABLE tokentransfer ADD COLUMN tx_type VARCHAR(64); +ALTER TABLE tokentransfer ADD COLUMN tx_id UUID; diff --git a/db/migrations/sqlite/000057_add_tokentransfer_blockchainevent.up.sql b/db/migrations/sqlite/000057_add_tokentransfer_blockchainevent.up.sql new file mode 100644 index 0000000000..acf28ccf3b --- /dev/null +++ b/db/migrations/sqlite/000057_add_tokentransfer_blockchainevent.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE tokentransfer DROP COLUMN tx_type; +ALTER TABLE tokentransfer DROP COLUMN tx_id; +ALTER TABLE tokentransfer ADD COLUMN blockchain_event UUID; diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index f51204b902..9311f7695c 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -1701,6 +1701,16 @@ paths: name: namespace schema: type: string + - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' + in: query + name: protocolid + schema: + type: string + - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' + in: query + name: source + schema: + type: string - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' in: query name: subscription @@ -1711,6 +1721,16 @@ paths: name: timestamp schema: type: string + - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' + in: query + name: tx.id + schema: + type: string + - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' + in: query + name: tx.type + schema: + type: string - description: Sort field. For multi-field sort use comma separated values (or multiple query values) with '-' prefix for descending in: query @@ -1759,14 +1779,24 @@ paths: type: string namespace: type: string - outputs: + output: additionalProperties: {} type: object + protocolId: + type: string sequence: format: int64 type: integer + source: + type: string subscription: {} timestamp: {} + tx: + properties: + id: {} + type: + type: string + type: object type: object description: Success default: @@ -1810,14 +1840,24 @@ paths: type: string namespace: type: string - outputs: + output: additionalProperties: {} type: object + protocolId: + type: string sequence: format: int64 type: integer + source: + type: string subscription: {} timestamp: {} + tx: + properties: + id: {} + type: + type: string + type: object type: object description: Success default: @@ -4416,7 +4456,7 @@ paths: - contract_interface_rejected - contract_api_confirmed - contract_api_rejected - - contract_event + - blockchain_event type: string type: object description: Success @@ -4476,7 +4516,7 @@ paths: - contract_interface_rejected - contract_api_confirmed - contract_api_rejected - - contract_event + - blockchain_event type: string type: object description: Success @@ -5147,7 +5187,7 @@ paths: - contract_interface_rejected - contract_api_confirmed - contract_api_rejected - - contract_event + - blockchain_event type: string type: object description: Success @@ -5250,30 +5290,18 @@ paths: schema: properties: created: {} - hash: {} id: {} - info: - additionalProperties: {} - type: object - protocolId: + namespace: type: string status: type: string - subject: - properties: - namespace: - type: string - reference: {} - signer: - type: string - type: - enum: - - none - - batch_pin - - token_pool - - token_transfer - type: string - type: object + type: + enum: + - none + - batch_pin + - token_pool + - token_transfer + type: string type: object description: Success default: @@ -7532,6 +7560,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -7540,23 +7569,88 @@ paths: key: type: string localId: {} - message: {} + message: + properties: + batch: {} + confirmed: {} + data: + items: + properties: + hash: {} + id: {} + type: object + type: array + group: + properties: + ledger: {} + members: + items: + properties: + identity: + type: string + node: + type: string + type: object + type: array + name: + type: string + type: object + hash: {} + header: + properties: + author: + type: string + cid: {} + created: {} + datahash: {} + group: {} + id: {} + key: + type: string + namespace: + type: string + tag: + type: string + topics: + items: + type: string + type: array + txtype: + type: string + type: + enum: + - definition + - broadcast + - private + - groupinit + - transfer_broadcast + - transfer_private + type: string + type: object + pins: + items: + type: string + type: array + state: + enum: + - staged + - ready + - pending + - confirmed + - rejected + type: string + type: object messageHash: {} namespace: type: string - pool: {} + pool: + type: string protocolId: type: string to: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -7573,6 +7667,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -7592,12 +7687,6 @@ paths: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -7614,6 +7703,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -7633,12 +7723,6 @@ paths: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -7694,6 +7778,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -7702,23 +7787,88 @@ paths: key: type: string localId: {} - message: {} + message: + properties: + batch: {} + confirmed: {} + data: + items: + properties: + hash: {} + id: {} + type: object + type: array + group: + properties: + ledger: {} + members: + items: + properties: + identity: + type: string + node: + type: string + type: object + type: array + name: + type: string + type: object + hash: {} + header: + properties: + author: + type: string + cid: {} + created: {} + datahash: {} + group: {} + id: {} + key: + type: string + namespace: + type: string + tag: + type: string + topics: + items: + type: string + type: array + txtype: + type: string + type: + enum: + - definition + - broadcast + - private + - groupinit + - transfer_broadcast + - transfer_private + type: string + type: object + pins: + items: + type: string + type: array + state: + enum: + - staged + - ready + - pending + - confirmed + - rejected + type: string + type: object messageHash: {} namespace: type: string - pool: {} + pool: + type: string protocolId: type: string to: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -7735,6 +7885,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -7754,12 +7905,6 @@ paths: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -7776,6 +7921,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -7795,12 +7941,6 @@ paths: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -7850,6 +7990,11 @@ paths: name: amount schema: type: string + - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' + in: query + name: blockchainevent + schema: + type: string - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' in: query name: connector @@ -7956,6 +8101,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -7975,12 +8121,6 @@ paths: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -8035,6 +8175,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -8043,23 +8184,88 @@ paths: key: type: string localId: {} - message: {} + message: + properties: + batch: {} + confirmed: {} + data: + items: + properties: + hash: {} + id: {} + type: object + type: array + group: + properties: + ledger: {} + members: + items: + properties: + identity: + type: string + node: + type: string + type: object + type: array + name: + type: string + type: object + hash: {} + header: + properties: + author: + type: string + cid: {} + created: {} + datahash: {} + group: {} + id: {} + key: + type: string + namespace: + type: string + tag: + type: string + topics: + items: + type: string + type: array + txtype: + type: string + type: + enum: + - definition + - broadcast + - private + - groupinit + - transfer_broadcast + - transfer_private + type: string + type: object + pins: + items: + type: string + type: array + state: + enum: + - staged + - ready + - pending + - confirmed + - rejected + type: string + type: object messageHash: {} namespace: type: string - pool: {} + pool: + type: string protocolId: type: string to: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -8076,6 +8282,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -8095,12 +8302,6 @@ paths: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -8117,6 +8318,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -8136,12 +8338,6 @@ paths: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -8515,6 +8711,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -8523,30 +8720,95 @@ paths: key: type: string localId: {} - message: {} - messageHash: {} - namespace: - type: string - pool: {} - protocolId: - type: string - to: - type: string - tokenIndex: - type: string - tx: + message: properties: - id: {} - type: - type: string - type: object - type: - enum: - - mint - - burn - - transfer - type: string - uri: + batch: {} + confirmed: {} + data: + items: + properties: + hash: {} + id: {} + type: object + type: array + group: + properties: + ledger: {} + members: + items: + properties: + identity: + type: string + node: + type: string + type: object + type: array + name: + type: string + type: object + hash: {} + header: + properties: + author: + type: string + cid: {} + created: {} + datahash: {} + group: {} + id: {} + key: + type: string + namespace: + type: string + tag: + type: string + topics: + items: + type: string + type: array + txtype: + type: string + type: + enum: + - definition + - broadcast + - private + - groupinit + - transfer_broadcast + - transfer_private + type: string + type: object + pins: + items: + type: string + type: array + state: + enum: + - staged + - ready + - pending + - confirmed + - rejected + type: string + type: object + messageHash: {} + namespace: + type: string + pool: + type: string + protocolId: + type: string + to: + type: string + tokenIndex: + type: string + type: + enum: + - mint + - burn + - transfer + type: string + uri: type: string type: object responses: @@ -8556,6 +8818,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -8575,12 +8838,6 @@ paths: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -8597,6 +8854,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -8616,12 +8874,6 @@ paths: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -8695,6 +8947,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -8703,23 +8956,88 @@ paths: key: type: string localId: {} - message: {} + message: + properties: + batch: {} + confirmed: {} + data: + items: + properties: + hash: {} + id: {} + type: object + type: array + group: + properties: + ledger: {} + members: + items: + properties: + identity: + type: string + node: + type: string + type: object + type: array + name: + type: string + type: object + hash: {} + header: + properties: + author: + type: string + cid: {} + created: {} + datahash: {} + group: {} + id: {} + key: + type: string + namespace: + type: string + tag: + type: string + topics: + items: + type: string + type: array + txtype: + type: string + type: + enum: + - definition + - broadcast + - private + - groupinit + - transfer_broadcast + - transfer_private + type: string + type: object + pins: + items: + type: string + type: array + state: + enum: + - staged + - ready + - pending + - confirmed + - rejected + type: string + type: object messageHash: {} namespace: type: string - pool: {} + pool: + type: string protocolId: type: string to: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -8736,6 +9054,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -8755,12 +9074,6 @@ paths: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -8777,6 +9090,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -8796,12 +9110,6 @@ paths: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -9211,6 +9519,11 @@ paths: name: amount schema: type: string + - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' + in: query + name: blockchainevent + schema: + type: string - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' in: query name: connector @@ -9317,6 +9630,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -9336,12 +9650,6 @@ paths: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -9383,6 +9691,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -9391,23 +9700,88 @@ paths: key: type: string localId: {} - message: {} + message: + properties: + batch: {} + confirmed: {} + data: + items: + properties: + hash: {} + id: {} + type: object + type: array + group: + properties: + ledger: {} + members: + items: + properties: + identity: + type: string + node: + type: string + type: object + type: array + name: + type: string + type: object + hash: {} + header: + properties: + author: + type: string + cid: {} + created: {} + datahash: {} + group: {} + id: {} + key: + type: string + namespace: + type: string + tag: + type: string + topics: + items: + type: string + type: array + txtype: + type: string + type: + enum: + - definition + - broadcast + - private + - groupinit + - transfer_broadcast + - transfer_private + type: string + type: object + pins: + items: + type: string + type: array + state: + enum: + - staged + - ready + - pending + - confirmed + - rejected + type: string + type: object messageHash: {} namespace: type: string - pool: {} + pool: + type: string protocolId: type: string to: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -9424,6 +9798,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -9443,12 +9818,6 @@ paths: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -9465,6 +9834,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -9484,12 +9854,6 @@ paths: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -9534,6 +9898,7 @@ paths: schema: properties: amount: {} + blockchainEvent: {} connector: type: string created: {} @@ -9553,12 +9918,6 @@ paths: type: string tokenIndex: type: string - tx: - properties: - id: {} - type: - type: string - type: object type: enum: - mint @@ -9600,36 +9959,11 @@ paths: name: id schema: type: string - - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' - in: query - name: info - schema: - type: string - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' in: query name: namespace schema: type: string - - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' - in: query - name: protocolid - schema: - type: string - - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' - in: query - name: reference - schema: - type: string - - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' - in: query - name: sequence - schema: - type: string - - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' - in: query - name: signer - schema: - type: string - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' in: query name: status @@ -9681,30 +10015,18 @@ paths: schema: properties: created: {} - hash: {} id: {} - info: - additionalProperties: {} - type: object - protocolId: + namespace: type: string status: type: string - subject: - properties: - namespace: - type: string - reference: {} - signer: - type: string - type: - enum: - - none - - batch_pin - - token_pool - - token_transfer - type: string - type: object + type: + enum: + - none + - batch_pin + - token_pool + - token_transfer + type: string type: object description: Success default: @@ -9744,36 +10066,11 @@ paths: name: id schema: type: string - - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' - in: query - name: info - schema: - type: string - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' in: query name: namespace schema: type: string - - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' - in: query - name: protocolid - schema: - type: string - - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' - in: query - name: reference - schema: - type: string - - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' - in: query - name: sequence - schema: - type: string - - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' - in: query - name: signer - schema: - type: string - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' in: query name: status @@ -9825,30 +10122,18 @@ paths: schema: properties: created: {} - hash: {} id: {} - info: - additionalProperties: {} - type: object - protocolId: + namespace: type: string status: type: string - subject: - properties: - namespace: - type: string - reference: {} - signer: - type: string - type: - enum: - - none - - batch_pin - - token_pool - - token_transfer - type: string - type: object + type: + enum: + - none + - batch_pin + - token_pool + - token_transfer + type: string type: object description: Success default: @@ -9886,30 +10171,18 @@ paths: items: properties: created: {} - hash: {} id: {} - info: - additionalProperties: {} - type: object - protocolId: + namespace: type: string status: type: string - subject: - properties: - namespace: - type: string - reference: {} - signer: - type: string - type: - enum: - - none - - batch_pin - - token_pool - - token_transfer - type: string - type: object + type: + enum: + - none + - batch_pin + - token_pool + - token_transfer + type: string type: object type: array description: Success diff --git a/internal/apiserver/route_get_contract_event_by_id.go b/internal/apiserver/route_get_contract_event_by_id.go index 6773152d47..7251eb25d3 100644 --- a/internal/apiserver/route_get_contract_event_by_id.go +++ b/internal/apiserver/route_get_contract_event_by_id.go @@ -38,7 +38,7 @@ var getContractEventByID = &oapispec.Route{ Description: i18n.MsgTBD, JSONInputValue: nil, JSONInputMask: nil, - JSONOutputValue: func() interface{} { return &fftypes.ContractEvent{} }, + JSONOutputValue: func() interface{} { return &fftypes.BlockchainEvent{} }, JSONOutputCodes: []int{http.StatusOK}, JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) { u, err := fftypes.ParseUUID(r.Ctx, r.PP["id"]) diff --git a/internal/apiserver/route_get_contract_event_by_id_test.go b/internal/apiserver/route_get_contract_event_by_id_test.go index 65380c2011..7881287f60 100644 --- a/internal/apiserver/route_get_contract_event_by_id_test.go +++ b/internal/apiserver/route_get_contract_event_by_id_test.go @@ -36,7 +36,7 @@ func TestGetContractEventByID(t *testing.T) { res := httptest.NewRecorder() mcm.On("GetContractEventByID", mock.Anything, id). - Return(&fftypes.ContractEvent{}, nil) + Return(&fftypes.BlockchainEvent{}, nil) r.ServeHTTP(res, req) assert.Equal(t, 200, res.Result().StatusCode) diff --git a/internal/apiserver/route_get_contract_events.go b/internal/apiserver/route_get_contract_events.go index e3b93dee40..b51776a753 100644 --- a/internal/apiserver/route_get_contract_events.go +++ b/internal/apiserver/route_get_contract_events.go @@ -34,11 +34,11 @@ var getContractEvents = &oapispec.Route{ {Name: "ns", ExampleFromConf: config.NamespacesDefault, Description: i18n.MsgTBD}, }, QueryParams: nil, - FilterFactory: database.ContractEventQueryFactory, + FilterFactory: database.BlockchainEventQueryFactory, Description: i18n.MsgTBD, JSONInputValue: nil, JSONInputMask: nil, - JSONOutputValue: func() interface{} { return []*fftypes.ContractEvent{} }, + JSONOutputValue: func() interface{} { return []*fftypes.BlockchainEvent{} }, JSONOutputCodes: []int{http.StatusOK}, JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) { return filterResult(getOr(r.Ctx).Contracts().GetContractEvents(r.Ctx, r.PP["ns"], r.Filter)) diff --git a/internal/apiserver/route_get_contract_events_test.go b/internal/apiserver/route_get_contract_events_test.go index 9a635d0429..6a0ad93257 100644 --- a/internal/apiserver/route_get_contract_events_test.go +++ b/internal/apiserver/route_get_contract_events_test.go @@ -35,7 +35,7 @@ func TestGetContractEvents(t *testing.T) { res := httptest.NewRecorder() mcm.On("GetContractEvents", mock.Anything, "mynamespace", mock.Anything). - Return([]*fftypes.ContractEvent{}, nil, nil) + Return([]*fftypes.BlockchainEvent{}, nil, nil) r.ServeHTTP(res, req) assert.Equal(t, 200, res.Result().StatusCode) diff --git a/internal/assets/manager.go b/internal/assets/manager.go index 8921bf6917..b692dd734a 100644 --- a/internal/assets/manager.go +++ b/internal/assets/manager.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -28,7 +28,6 @@ import ( "github.com/hyperledger/firefly/internal/retry" "github.com/hyperledger/firefly/internal/syncasync" "github.com/hyperledger/firefly/internal/sysmessaging" - "github.com/hyperledger/firefly/internal/txcommon" "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/hyperledger/firefly/pkg/tokens" @@ -36,7 +35,7 @@ import ( type Manager interface { CreateTokenPool(ctx context.Context, ns string, pool *fftypes.TokenPool, waitConfirm bool) (*fftypes.TokenPool, error) - ActivateTokenPool(ctx context.Context, pool *fftypes.TokenPool, tx *fftypes.Transaction) error + ActivateTokenPool(ctx context.Context, pool *fftypes.TokenPool, event *fftypes.BlockchainEvent) error GetTokenPools(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.TokenPool, *database.FilterResult, error) GetTokenPool(ctx context.Context, ns, connector, poolName string) (*fftypes.TokenPool, error) GetTokenPoolByNameOrID(ctx context.Context, ns string, poolNameOrID string) (*fftypes.TokenPool, error) @@ -78,7 +77,6 @@ type assetManager struct { messaging privatemessaging.Manager tokens map[string]tokens.Plugin retry retry.Retry - txhelper txcommon.Helper } func NewAssetManager(ctx context.Context, di database.Plugin, im identity.Manager, dm data.Manager, sa syncasync.Bridge, bm broadcast.Manager, pm privatemessaging.Manager, ti map[string]tokens.Plugin) (Manager, error) { @@ -99,7 +97,6 @@ func NewAssetManager(ctx context.Context, di database.Plugin, im identity.Manage MaximumDelay: config.GetDuration(config.AssetManagerRetryMaxDelay), Factor: config.GetFloat64(config.AssetManagerRetryFactor), }, - txhelper: txcommon.NewTransactionHelper(di), } return am, nil } diff --git a/internal/assets/token_pool.go b/internal/assets/token_pool.go index 6efc599781..ceb4d8ec26 100644 --- a/internal/assets/token_pool.go +++ b/internal/assets/token_pool.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -85,19 +85,14 @@ func (am *assetManager) createTokenPoolInternal(ctx context.Context, pool *fftyp } tx := &fftypes.Transaction{ - ID: fftypes.NewUUID(), - Subject: fftypes.TransactionSubject{ - Namespace: pool.Namespace, - Type: fftypes.TransactionTypeTokenPool, - Signer: pool.Key, - Reference: pool.ID, - }, - Created: fftypes.Now(), - Status: fftypes.OpStatusPending, - } - tx.Hash = tx.Subject.Hash() + ID: fftypes.NewUUID(), + Namespace: pool.Namespace, + Type: fftypes.TransactionTypeTokenPool, + Created: fftypes.Now(), + Status: fftypes.OpStatusPending, + } pool.TX.ID = tx.ID - pool.TX.Type = tx.Subject.Type + pool.TX.Type = tx.Type op := fftypes.NewTXOperation( plugin, @@ -109,7 +104,7 @@ func (am *assetManager) createTokenPoolInternal(ctx context.Context, pool *fftyp txcommon.AddTokenPoolCreateInputs(op, pool) err = am.database.RunAsGroup(ctx, func(ctx context.Context) (err error) { - err = am.database.UpsertTransaction(ctx, tx, false /* should be new, or idempotent replay */) + err = am.database.UpsertTransaction(ctx, tx) if err == nil { err = am.database.InsertOperation(ctx, op) } @@ -122,12 +117,12 @@ func (am *assetManager) createTokenPoolInternal(ctx context.Context, pool *fftyp return pool, plugin.CreateTokenPool(ctx, op.ID, pool) } -func (am *assetManager) ActivateTokenPool(ctx context.Context, pool *fftypes.TokenPool, tx *fftypes.Transaction) error { +func (am *assetManager) ActivateTokenPool(ctx context.Context, pool *fftypes.TokenPool, event *fftypes.BlockchainEvent) error { plugin, err := am.selectTokenPlugin(ctx, pool.Connector) if err != nil { return err } - return plugin.ActivateTokenPool(ctx, nil, pool, tx) + return plugin.ActivateTokenPool(ctx, nil, pool, event) } func (am *assetManager) GetTokenPools(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.TokenPool, *database.FilterResult, error) { diff --git a/internal/assets/token_pool_test.go b/internal/assets/token_pool_test.go index b5ef820ca8..bbff4ef2e6 100644 --- a/internal/assets/token_pool_test.go +++ b/internal/assets/token_pool_test.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in comdiliance with the License. @@ -92,8 +92,8 @@ func TestCreateTokenPoolFail(t *testing.T) { mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenPool - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenPool + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) @@ -114,7 +114,7 @@ func TestCreateTokenPoolTransactionFail(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) - mdi.On("UpsertTransaction", context.Background(), mock.Anything, false).Return(fmt.Errorf("pop")) + mdi.On("UpsertTransaction", context.Background(), mock.Anything).Return(fmt.Errorf("pop")) _, err := am.CreateTokenPool(context.Background(), "ns1", pool, false) assert.Regexp(t, "pop", err) @@ -134,8 +134,8 @@ func TestCreateTokenPoolOperationFail(t *testing.T) { mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenPool - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenPool + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(fmt.Errorf("pop")) _, err := am.CreateTokenPool(context.Background(), "ns1", pool, false) @@ -158,8 +158,8 @@ func TestCreateTokenPoolSuccess(t *testing.T) { mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenPool - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenPool + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) _, err := am.CreateTokenPool(context.Background(), "ns1", pool, false) @@ -182,8 +182,8 @@ func TestCreateTokenPoolUnknownConnectorSuccess(t *testing.T) { mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenPool - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenPool + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) _, err := am.CreateTokenPool(context.Background(), "ns1", pool, false) @@ -242,8 +242,8 @@ func TestCreateTokenPoolConfirm(t *testing.T) { mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil).Times(2) mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything).Return(nil).Times(1) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenPool - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenPool + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil).Times(1) msa.On("WaitForTokenPool", context.Background(), "ns1", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { @@ -320,8 +320,8 @@ func TestCreateTokenPoolByTypeFail(t *testing.T) { mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenPool - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenPool + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) @@ -342,7 +342,7 @@ func TestCreateTokenPoolByTypeTransactionFail(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) - mdi.On("UpsertTransaction", context.Background(), mock.Anything, false).Return(fmt.Errorf("pop")) + mdi.On("UpsertTransaction", context.Background(), mock.Anything).Return(fmt.Errorf("pop")) _, err := am.CreateTokenPoolByType(context.Background(), "ns1", "magic-tokens", pool, false) assert.Regexp(t, "pop", err) @@ -362,8 +362,8 @@ func TestCreateTokenPoolByTypeOperationFail(t *testing.T) { mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenPool - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenPool + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(fmt.Errorf("pop")) _, err := am.CreateTokenPoolByType(context.Background(), "ns1", "magic-tokens", pool, false) @@ -386,8 +386,8 @@ func TestCreateTokenPoolByTypeSuccess(t *testing.T) { mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenPool - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenPool + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) _, err := am.CreateTokenPoolByType(context.Background(), "ns1", "magic-tokens", pool, false) @@ -411,8 +411,8 @@ func TestCreateTokenPoolByTypeConfirm(t *testing.T) { mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil).Times(2) mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything).Return(nil).Times(1) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenPool - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenPool + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil).Times(1) msa.On("WaitForTokenPool", context.Background(), "ns1", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { @@ -433,14 +433,14 @@ func TestActivateTokenPool(t *testing.T) { Namespace: "ns1", Connector: "magic-tokens", } - tx := &fftypes.Transaction{} + ev := &fftypes.BlockchainEvent{} mdm := am.data.(*datamocks.Manager) mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) - mti.On("ActivateTokenPool", context.Background(), mock.Anything, pool, tx).Return(nil) + mti.On("ActivateTokenPool", context.Background(), mock.Anything, pool, ev).Return(nil) - err := am.ActivateTokenPool(context.Background(), pool, tx) + err := am.ActivateTokenPool(context.Background(), pool, ev) assert.NoError(t, err) } @@ -452,12 +452,12 @@ func TestActivateTokenPoolBadConnector(t *testing.T) { Namespace: "ns1", Connector: "bad", } - tx := &fftypes.Transaction{} + ev := &fftypes.BlockchainEvent{} mdm := am.data.(*datamocks.Manager) mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) - err := am.ActivateTokenPool(context.Background(), pool, tx) + err := am.ActivateTokenPool(context.Background(), pool, ev) assert.Regexp(t, "FF10272", err) } diff --git a/internal/assets/token_transfer.go b/internal/assets/token_transfer.go index ad44558186..6dc366d558 100644 --- a/internal/assets/token_transfer.go +++ b/internal/assets/token_transfer.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -247,19 +247,12 @@ func (s *transferSender) sendInternal(ctx context.Context, method sendMethod) er } tx := &fftypes.Transaction{ - ID: fftypes.NewUUID(), - Subject: fftypes.TransactionSubject{ - Namespace: s.namespace, - Type: fftypes.TransactionTypeTokenTransfer, - Signer: s.transfer.Key, - Reference: s.transfer.LocalID, - }, - Created: fftypes.Now(), - Status: fftypes.OpStatusPending, + ID: fftypes.NewUUID(), + Namespace: s.namespace, + Type: fftypes.TransactionTypeTokenTransfer, + Created: fftypes.Now(), + Status: fftypes.OpStatusPending, } - tx.Hash = tx.Subject.Hash() - s.transfer.TX.ID = tx.ID - s.transfer.TX.Type = tx.Subject.Type op := fftypes.NewTXOperation( plugin, @@ -280,7 +273,7 @@ func (s *transferSender) sendInternal(ctx context.Context, method sendMethod) er return i18n.NewError(ctx, i18n.MsgTokenPoolNotConfirmed) } - err = s.mgr.database.UpsertTransaction(ctx, tx, false /* should be new, or idempotent replay */) + err = s.mgr.database.UpsertTransaction(ctx, tx) if err != nil { return err } @@ -299,11 +292,11 @@ func (s *transferSender) sendInternal(ctx context.Context, method sendMethod) er switch s.transfer.Type { case fftypes.TokenTransferTypeMint: - err = plugin.MintTokens(ctx, op.ID, pool.ProtocolID, &s.transfer.TokenTransfer) + err = plugin.MintTokens(ctx, op.ID, tx.ID, pool.ProtocolID, &s.transfer.TokenTransfer) case fftypes.TokenTransferTypeTransfer: - err = plugin.TransferTokens(ctx, op.ID, pool.ProtocolID, &s.transfer.TokenTransfer) + err = plugin.TransferTokens(ctx, op.ID, tx.ID, pool.ProtocolID, &s.transfer.TokenTransfer) case fftypes.TokenTransferTypeBurn: - err = plugin.BurnTokens(ctx, op.ID, pool.ProtocolID, &s.transfer.TokenTransfer) + err = plugin.BurnTokens(ctx, op.ID, tx.ID, pool.ProtocolID, &s.transfer.TokenTransfer) default: panic(fmt.Sprintf("unknown transfer type: %v", s.transfer.Type)) } diff --git a/internal/assets/token_transfer_test.go b/internal/assets/token_transfer_test.go index bde332759e..3c4026c88f 100644 --- a/internal/assets/token_transfer_test.go +++ b/internal/assets/token_transfer_test.go @@ -126,10 +126,10 @@ func TestMintTokensSuccess(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(pool, nil) - mti.On("MintTokens", context.Background(), mock.Anything, "F1", &mint.TokenTransfer).Return(nil) + mti.On("MintTokens", context.Background(), mock.Anything, mock.Anything, "F1", &mint.TokenTransfer).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) _, err := am.MintTokens(context.Background(), "ns1", mint, false) @@ -156,10 +156,10 @@ func TestMintTokenUnknownConnectorSuccess(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(pool, nil) - mti.On("MintTokens", context.Background(), mock.Anything, "F1", &mint.TokenTransfer).Return(nil) + mti.On("MintTokens", context.Background(), mock.Anything, mock.Anything, "F1", &mint.TokenTransfer).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) _, err := am.MintTokens(context.Background(), "ns1", mint, false) @@ -277,10 +277,10 @@ func TestMintTokenUnknownPoolSuccess(t *testing.T) { return info.Count && info.Limit == 1 }))).Return(tokenPools, filterResult, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(tokenPools[0], nil) - mti.On("MintTokens", context.Background(), mock.Anything, "F1", &mint.TokenTransfer).Return(nil) + mti.On("MintTokens", context.Background(), mock.Anything, mock.Anything, "F1", &mint.TokenTransfer).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) _, err := am.MintTokens(context.Background(), "ns1", mint, false) @@ -449,10 +449,10 @@ func TestMintTokensFail(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(pool, nil) - mti.On("MintTokens", context.Background(), mock.Anything, "F1", &mint.TokenTransfer).Return(fmt.Errorf("pop")) + mti.On("MintTokens", context.Background(), mock.Anything, mock.Anything, "F1", &mint.TokenTransfer).Return(fmt.Errorf("pop")) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) mdi.On("UpdateTransaction", context.Background(), mock.Anything, mock.Anything).Return(nil) mdi.On("UpdateOperation", context.Background(), mock.Anything, mock.Anything).Return(nil) @@ -481,11 +481,11 @@ func TestMintTokensFailAndDbFail(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(pool, nil) - mti.On("MintTokens", context.Background(), mock.Anything, "F1", &mint.TokenTransfer).Return(fmt.Errorf("pop")) + mti.On("MintTokens", context.Background(), mock.Anything, mock.Anything, "F1", &mint.TokenTransfer).Return(fmt.Errorf("pop")) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer && tx.Status != fftypes.OpStatusFailed - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenTransfer && tx.Status != fftypes.OpStatusFailed + })).Return(nil) mdi.On("UpdateTransaction", context.Background(), mock.Anything, mock.Anything).Return(fmt.Errorf("Update fail")) mdi.On("UpdateOperation", context.Background(), mock.Anything, mock.Anything).Return(fmt.Errorf("Update fail")) @@ -513,8 +513,8 @@ func TestMintTokensOperationFail(t *testing.T) { mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(pool, nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(fmt.Errorf("pop")) _, err := am.MintTokens(context.Background(), "ns1", mint, false) @@ -543,10 +543,10 @@ func TestMintTokensConfirm(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(pool, nil) - mti.On("MintTokens", context.Background(), mock.Anything, "F1", &mint.TokenTransfer).Return(nil) + mti.On("MintTokens", context.Background(), mock.Anything, mock.Anything, "F1", &mint.TokenTransfer).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) msa.On("WaitForTokenTransfer", context.Background(), "ns1", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { @@ -583,10 +583,10 @@ func TestMintTokensByTypeSuccess(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(pool, nil) - mti.On("MintTokens", context.Background(), mock.Anything, "F1", &mint.TokenTransfer).Return(nil) + mti.On("MintTokens", context.Background(), mock.Anything, mock.Anything, "F1", &mint.TokenTransfer).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) @@ -613,10 +613,10 @@ func TestBurnTokensSuccess(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(pool, nil) - mti.On("BurnTokens", context.Background(), mock.Anything, "F1", &burn.TokenTransfer).Return(nil) + mti.On("BurnTokens", context.Background(), mock.Anything, mock.Anything, "F1", &burn.TokenTransfer).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) _, err := am.BurnTokens(context.Background(), "ns1", burn, false) @@ -667,10 +667,10 @@ func TestBurnTokensConfirm(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(pool, nil) - mti.On("BurnTokens", context.Background(), mock.Anything, "F1", &burn.TokenTransfer).Return(nil) + mti.On("BurnTokens", context.Background(), mock.Anything, mock.Anything, "F1", &burn.TokenTransfer).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) msa.On("WaitForTokenTransfer", context.Background(), "ns1", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { @@ -707,10 +707,10 @@ func TestBurnTokensByTypeSuccess(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(pool, nil) - mti.On("BurnTokens", context.Background(), mock.Anything, "F1", &burn.TokenTransfer).Return(nil) + mti.On("BurnTokens", context.Background(), mock.Anything, mock.Anything, "F1", &burn.TokenTransfer).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) _, err := am.BurnTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", burn, false) @@ -743,10 +743,10 @@ func TestTransferTokensSuccess(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(pool, nil) - mti.On("TransferTokens", context.Background(), mock.Anything, "F1", &transfer.TokenTransfer).Return(nil) + mti.On("TransferTokens", context.Background(), mock.Anything, mock.Anything, "F1", &transfer.TokenTransfer).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) @@ -844,8 +844,8 @@ func TestTransferTokensInvalidType(t *testing.T) { mdi := am.database.(*databasemocks.Plugin) mdi.On("GetTokenPool", am.ctx, "ns1", "pool1").Return(pool, nil) mdi.On("UpsertTransaction", am.ctx, mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) mdi.On("InsertOperation", am.ctx, mock.Anything).Return(nil) sender := &transferSender{ @@ -880,8 +880,8 @@ func TestTransferTokensTransactionFail(t *testing.T) { mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(pool, nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(fmt.Errorf("pop")) + return tx.Type == fftypes.TransactionTypeTokenTransfer + })).Return(fmt.Errorf("pop")) _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) assert.EqualError(t, err, "pop") @@ -929,10 +929,10 @@ func TestTransferTokensWithBroadcastMessage(t *testing.T) { mms := &sysmessagingmocks.MessageSender{} mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(pool, nil) - mti.On("TransferTokens", context.Background(), mock.Anything, "F1", &transfer.TokenTransfer).Return(nil) + mti.On("TransferTokens", context.Background(), mock.Anything, mock.Anything, "F1", &transfer.TokenTransfer).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) mbm.On("NewBroadcast", "ns1", transfer.Message).Return(mms) mms.On("Prepare", context.Background()).Return(nil) @@ -1027,10 +1027,10 @@ func TestTransferTokensWithPrivateMessage(t *testing.T) { mms := &sysmessagingmocks.MessageSender{} mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(pool, nil) - mti.On("TransferTokens", context.Background(), mock.Anything, "F1", &transfer.TokenTransfer).Return(nil) + mti.On("TransferTokens", context.Background(), mock.Anything, mock.Anything, "F1", &transfer.TokenTransfer).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) mpm.On("NewMessage", "ns1", transfer.Message).Return(mms) mms.On("Prepare", context.Background()).Return(nil) @@ -1108,10 +1108,10 @@ func TestTransferTokensConfirm(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(pool, nil) - mti.On("TransferTokens", context.Background(), mock.Anything, "F1", &transfer.TokenTransfer).Return(nil) + mti.On("TransferTokens", context.Background(), mock.Anything, mock.Anything, "F1", &transfer.TokenTransfer).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) msa.On("WaitForTokenTransfer", context.Background(), "ns1", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { @@ -1169,11 +1169,11 @@ func TestTransferTokensWithBroadcastConfirm(t *testing.T) { msa := am.syncasync.(*syncasyncmocks.Bridge) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(pool, nil) - mti.On("TransferTokens", context.Background(), mock.Anything, "F1", &transfer.TokenTransfer).Return(nil) + mti.On("TransferTokens", context.Background(), mock.Anything, mock.Anything, "F1", &transfer.TokenTransfer).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) mbm.On("NewBroadcast", "ns1", transfer.Message).Return(mms) mms.On("Prepare", context.Background()).Return(nil) mdi.On("UpsertMessage", context.Background(), mock.MatchedBy(func(msg *fftypes.Message) bool { @@ -1226,10 +1226,10 @@ func TestTransferTokensByTypeSuccess(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(pool, nil) - mti.On("TransferTokens", context.Background(), mock.Anything, "F1", &transfer.TokenTransfer).Return(nil) + mti.On("TransferTokens", context.Background(), mock.Anything, mock.Anything, "F1", &transfer.TokenTransfer).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) diff --git a/internal/batchpin/batchpin.go b/internal/batchpin/batchpin.go index 9888c07bea..9e74cda99d 100644 --- a/internal/batchpin/batchpin.go +++ b/internal/batchpin/batchpin.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -49,18 +49,13 @@ func NewBatchPinSubmitter(di database.Plugin, im identity.Manager, bi blockchain func (bp *batchPinSubmitter) SubmitPinnedBatch(ctx context.Context, batch *fftypes.Batch, contexts []*fftypes.Bytes32) error { tx := &fftypes.Transaction{ - ID: batch.Payload.TX.ID, - Subject: fftypes.TransactionSubject{ - Type: fftypes.TransactionTypeBatchPin, - Namespace: batch.Namespace, - Signer: batch.Key, // The transaction records on the on-chain identity - Reference: batch.ID, - }, - Created: fftypes.Now(), - Status: fftypes.OpStatusPending, + ID: batch.Payload.TX.ID, + Type: fftypes.TransactionTypeBatchPin, + Namespace: batch.Namespace, + Created: fftypes.Now(), + Status: fftypes.OpStatusPending, } - tx.Hash = tx.Subject.Hash() - err := bp.database.UpsertTransaction(ctx, tx, false /* should be new, or idempotent replay */) + err := bp.database.UpsertTransaction(ctx, tx) if err != nil { return err } diff --git a/internal/batchpin/batchpin_test.go b/internal/batchpin/batchpin_test.go index fdc1abda10..712238dee5 100644 --- a/internal/batchpin/batchpin_test.go +++ b/internal/batchpin/batchpin_test.go @@ -62,7 +62,7 @@ func TestSubmitPinnedBatchOk(t *testing.T) { } contexts := []*fftypes.Bytes32{} - mdi.On("UpsertTransaction", ctx, mock.Anything, false).Return(nil) + mdi.On("UpsertTransaction", ctx, mock.Anything).Return(nil) mdi.On("InsertOperation", ctx, mock.MatchedBy(func(op *fftypes.Operation) bool { assert.Equal(t, fftypes.OpTypeBlockchainBatchPin, op.Type) assert.Equal(t, "ut", op.Plugin) @@ -98,7 +98,7 @@ func TestSubmitPinnedBatchWithMetricsOk(t *testing.T) { } contexts := []*fftypes.Bytes32{} - mdi.On("UpsertTransaction", ctx, mock.Anything, false).Return(nil) + mdi.On("UpsertTransaction", ctx, mock.Anything).Return(nil) mdi.On("InsertOperation", ctx, mock.MatchedBy(func(op *fftypes.Operation) bool { assert.Equal(t, fftypes.OpTypeBlockchainBatchPin, op.Type) assert.Equal(t, "ut", op.Plugin) @@ -132,7 +132,7 @@ func TestSubmitPinnedBatchOpFail(t *testing.T) { } contexts := []*fftypes.Bytes32{} - mdi.On("UpsertTransaction", ctx, mock.Anything, false).Return(nil) + mdi.On("UpsertTransaction", ctx, mock.Anything).Return(nil) mdi.On("InsertOperation", ctx, mock.Anything).Return(fmt.Errorf("pop")) err := bp.SubmitPinnedBatch(ctx, batch, contexts) @@ -161,7 +161,7 @@ func TestSubmitPinnedBatchTxInsertFail(t *testing.T) { } contexts := []*fftypes.Bytes32{} - mdi.On("UpsertTransaction", ctx, mock.Anything, false).Return(fmt.Errorf("pop")) + mdi.On("UpsertTransaction", ctx, mock.Anything).Return(fmt.Errorf("pop")) err := bp.SubmitPinnedBatch(ctx, batch, contexts) assert.Regexp(t, "pop", err) diff --git a/internal/blockchain/ethereum/ethereum.go b/internal/blockchain/ethereum/ethereum.go index 6f5b5e3873..94aedf5dbb 100644 --- a/internal/blockchain/ethereum/ethereum.go +++ b/internal/blockchain/ethereum/ethereum.go @@ -198,6 +198,12 @@ func (e *Ethereum) handleBatchPinEvent(ctx context.Context, msgJSON fftypes.JSON sBatchHash := dataJSON.GetString("batchHash") sPayloadRef := dataJSON.GetString("payloadRef") sContexts := dataJSON.GetStringArray("contexts") + timestampStr := msgJSON.GetString("timestamp") + timestamp, err := fftypes.ParseTimeString(timestampStr) + if err != nil { + log.L(ctx).Errorf("BatchPin event is not valid - missing timestamp: %+v", msgJSON) + return nil // move on + } if sBlockNumber == "" || sTransactionIndex == "" || @@ -243,6 +249,7 @@ func (e *Ethereum) handleBatchPinEvent(ctx context.Context, msgJSON fftypes.JSON contexts[i] = &hash } + delete(msgJSON, "data") batch := &blockchain.BatchPin{ Namespace: ns, TransactionID: &txnID, @@ -250,14 +257,22 @@ func (e *Ethereum) handleBatchPinEvent(ctx context.Context, msgJSON fftypes.JSON BatchHash: &batchHash, BatchPayloadRef: sPayloadRef, Contexts: contexts, + Event: blockchain.Event{ + Source: e.Name(), + Name: "BatchPin", + ProtocolID: sTransactionHash, + Output: dataJSON, + Info: msgJSON, + Timestamp: timestamp, + }, } // If there's an error dispatching the event, we must return the error and shutdown - delete(msgJSON, "data") - return e.callbacks.BatchPinComplete(batch, authorAddress, sTransactionHash, msgJSON) + return e.callbacks.BatchPinComplete(batch, authorAddress) } -func (e *Ethereum) handleContractEvent(msgJSON fftypes.JSONObject) (err error) { +func (e *Ethereum) handleContractEvent(ctx context.Context, msgJSON fftypes.JSONObject) (err error) { + sTransactionHash := msgJSON.GetString("transactionHash") sub := msgJSON.GetString("subId") signature := msgJSON.GetString("signature") dataJSON := msgJSON.GetObject("data") @@ -265,17 +280,23 @@ func (e *Ethereum) handleContractEvent(msgJSON fftypes.JSONObject) (err error) { timestampStr := msgJSON.GetString("timestamp") timestamp, err := fftypes.ParseTimeString(timestampStr) if err != nil { - return err + log.L(ctx).Errorf("Contract event is not valid - missing timestamp: %+v", msgJSON) + return err // move on } delete(msgJSON, "data") event := &blockchain.ContractEvent{ Subscription: sub, - Name: name, - Outputs: dataJSON, - Info: msgJSON, - Timestamp: timestamp, + Event: blockchain.Event{ + Source: e.Name(), + Name: name, + ProtocolID: sTransactionHash, + Output: dataJSON, + Info: msgJSON, + Timestamp: timestamp, + }, } + return e.callbacks.ContractEvent(event) } @@ -331,7 +352,7 @@ func (e *Ethereum) handleMessageBatch(ctx context.Context, messages []interface{ default: l.Infof("Ignoring event with unknown signature: %s", signature) } - } else if err := e.handleContractEvent(msgJSON); err != nil { + } else if err := e.handleContractEvent(ctx1, msgJSON); err != nil { return err } } diff --git a/internal/blockchain/ethereum/ethereum_test.go b/internal/blockchain/ethereum/ethereum_test.go index 559bc92899..ad6737c894 100644 --- a/internal/blockchain/ethereum/ethereum_test.go +++ b/internal/blockchain/ethereum/ethereum_test.go @@ -514,65 +514,65 @@ func TestHandleMessageBatchPinOK(t *testing.T) { data := fftypes.JSONAnyPtr(` [ { - "address": "0x1C197604587F046FD40684A8f21f4609FB811A7b", - "blockNumber": "38011", - "transactionIndex": "0x0", - "transactionHash": "0xc26df2bf1a733e9249372d61eb11bd8662d26c8129df76890b1beb2f6fa72628", - "data": { - "author": "0X91D2B4381A4CD5C7C0F27565A7D4B829844C8635", + "address": "0x1C197604587F046FD40684A8f21f4609FB811A7b", + "blockNumber": "38011", + "transactionIndex": "0x0", + "transactionHash": "0xc26df2bf1a733e9249372d61eb11bd8662d26c8129df76890b1beb2f6fa72628", + "data": { + "author": "0X91D2B4381A4CD5C7C0F27565A7D4B829844C8635", "namespace": "ns1", "uuids": "0xe19af8b390604051812d7597d19adfb9847d3bfd074249efb65d3fed15f5b0a6", - "batchHash": "0xd71eb138d74c229a388eb0e1abc03f4c7cbb21d4fc4b839fbf0ec73e4263f6be", - "payloadRef": "Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", + "batchHash": "0xd71eb138d74c229a388eb0e1abc03f4c7cbb21d4fc4b839fbf0ec73e4263f6be", + "payloadRef": "Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", "contexts": [ "0x68e4da79f805bca5b912bcda9c63d03e6e867108dabb9b944109aea541ef522a", "0x19b82093de5ce92a01e333048e877e2374354bf846dd034864ef6ffbd6438771" - ], - "timestamp": "1620576488" + ] }, - "subId": "sb-b5b97a4e-a317-4053-6400-1474650efcb5", - "signature": "BatchPin(address,uint256,string,bytes32,bytes32,string,bytes32[])", - "logIndex": "50" + "subId": "sb-b5b97a4e-a317-4053-6400-1474650efcb5", + "signature": "BatchPin(address,uint256,string,bytes32,bytes32,string,bytes32[])", + "logIndex": "50", + "timestamp": "1620576488" }, { - "address": "0x1C197604587F046FD40684A8f21f4609FB811A7b", - "blockNumber": "38011", - "transactionIndex": "0x1", - "transactionHash": "0x0c50dff0893e795293189d9cc5ba0d63c4020d8758ace4a69d02c9d6d43cb695", - "data": { - "author": "0x91d2b4381a4cd5c7c0f27565a7d4b829844c8635", + "address": "0x1C197604587F046FD40684A8f21f4609FB811A7b", + "blockNumber": "38011", + "transactionIndex": "0x1", + "transactionHash": "0x0c50dff0893e795293189d9cc5ba0d63c4020d8758ace4a69d02c9d6d43cb695", + "data": { + "author": "0x91d2b4381a4cd5c7c0f27565a7d4b829844c8635", "namespace": "ns1", "uuids": "0x8a578549e56b49f9bd78d731f22b08d7a04c7cc37d444c2ba3b054e21326697e", - "batchHash": "0x20e6ef9b9c4df7fdb77a7de1e00347f4b02d996f2e56a7db361038be7b32a154", - "payloadRef": "Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", - "timestamp": "1620576488", + "batchHash": "0x20e6ef9b9c4df7fdb77a7de1e00347f4b02d996f2e56a7db361038be7b32a154", + "payloadRef": "Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", "contexts": [ "0x8a63eb509713b0cf9250a8eee24ee2dfc4b37225e3ad5c29c95127699d382f85" ] }, - "subId": "sb-b5b97a4e-a317-4053-6400-1474650efcb5", - "signature": "BatchPin(address,uint256,string,bytes32,bytes32,string,bytes32[])", - "logIndex": "51" + "subId": "sb-b5b97a4e-a317-4053-6400-1474650efcb5", + "signature": "BatchPin(address,uint256,string,bytes32,bytes32,string,bytes32[])", + "logIndex": "51", + "timestamp": "1620576488" }, { - "address": "0x06d34B270F15a0d82913EFD0627B0F62Fd22ecd5", - "blockNumber": "38011", - "transactionIndex": "0x2", - "transactionHash": "0x0c50dff0893e795293189d9cc5ba0d63c4020d8758ace4a69d02c9d6d43cb695", - "data": { - "author": "0x91d2b4381a4cd5c7c0f27565a7d4b829844c8635", + "address": "0x06d34B270F15a0d82913EFD0627B0F62Fd22ecd5", + "blockNumber": "38011", + "transactionIndex": "0x2", + "transactionHash": "0x0c50dff0893e795293189d9cc5ba0d63c4020d8758ace4a69d02c9d6d43cb695", + "data": { + "author": "0x91d2b4381a4cd5c7c0f27565a7d4b829844c8635", "namespace": "ns1", "uuids": "0x8a578549e56b49f9bd78d731f22b08d7a04c7cc37d444c2ba3b054e21326697e", - "batchHash": "0x892b31099b8476c0692a5f2982ea23a0614949eacf292a64a358aa73ecd404b4", - "payloadRef": "Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", - "timestamp": "1620576488", + "batchHash": "0x892b31099b8476c0692a5f2982ea23a0614949eacf292a64a358aa73ecd404b4", + "payloadRef": "Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", "contexts": [ "0xdab67320f1a0d0f1da572975e3a9ab6ef0fed315771c99fea0bfb54886c1aa94" ] }, - "subId": "sb-b5b97a4e-a317-4053-6400-1474650efcb5", - "signature": "Random(address,uint256,bytes32,bytes32,bytes32)", - "logIndex": "51" + "subId": "sb-b5b97a4e-a317-4053-6400-1474650efcb5", + "signature": "Random(address,uint256,bytes32,bytes32,bytes32)", + "logIndex": "51", + "timestamp": "1620576488" } ]`) @@ -584,7 +584,7 @@ func TestHandleMessageBatchPinOK(t *testing.T) { ID: "sb-b5b97a4e-a317-4053-6400-1474650efcb5", } - em.On("BatchPinComplete", mock.Anything, "0x91d2b4381a4cd5c7c0f27565a7d4b829844c8635", mock.Anything, mock.Anything).Return(nil) + em.On("BatchPinComplete", mock.Anything, "0x91d2b4381a4cd5c7c0f27565a7d4b829844c8635", mock.Anything).Return(nil) var events []interface{} err := json.Unmarshal(data.Bytes(), &events) @@ -599,7 +599,6 @@ func TestHandleMessageBatchPinOK(t *testing.T) { assert.Equal(t, "d71eb138d74c229a388eb0e1abc03f4c7cbb21d4fc4b839fbf0ec73e4263f6be", b.BatchHash.String()) assert.Equal(t, "Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", b.BatchPayloadRef) assert.Equal(t, "0x91d2b4381a4cd5c7c0f27565a7d4b829844c8635", em.Calls[0].Arguments[1]) - assert.Equal(t, "0xc26df2bf1a733e9249372d61eb11bd8662d26c8129df76890b1beb2f6fa72628", em.Calls[0].Arguments[2]) assert.Len(t, b.Contexts, 2) assert.Equal(t, "68e4da79f805bca5b912bcda9c63d03e6e867108dabb9b944109aea541ef522a", b.Contexts[0].String()) assert.Equal(t, "19b82093de5ce92a01e333048e877e2374354bf846dd034864ef6ffbd6438771", b.Contexts[1].String()) @@ -612,8 +611,11 @@ func TestHandleMessageBatchPinOK(t *testing.T) { "subId": "sb-b5b97a4e-a317-4053-6400-1474650efcb5", "transactionHash": "0xc26df2bf1a733e9249372d61eb11bd8662d26c8129df76890b1beb2f6fa72628", "transactionIndex": "0x0", + "timestamp": "1620576488", } - assert.Equal(t, info1, em.Calls[0].Arguments[3]) + assert.Equal(t, info1, b.Event.Info) + + b2 := em.Calls[1].Arguments[0].(*blockchain.BatchPin) info2 := fftypes.JSONObject{ "address": "0x1C197604587F046FD40684A8f21f4609FB811A7b", "blockNumber": "38011", @@ -622,8 +624,9 @@ func TestHandleMessageBatchPinOK(t *testing.T) { "subId": "sb-b5b97a4e-a317-4053-6400-1474650efcb5", "transactionHash": "0x0c50dff0893e795293189d9cc5ba0d63c4020d8758ace4a69d02c9d6d43cb695", "transactionIndex": "0x1", + "timestamp": "1620576488", } - assert.Equal(t, info2, em.Calls[1].Arguments[3]) + assert.Equal(t, info2, b2.Event.Info) em.AssertExpectations(t) @@ -633,25 +636,25 @@ func TestHandleMessageEmptyPayloadRef(t *testing.T) { data := fftypes.JSONAnyPtr(` [ { - "address": "0x1C197604587F046FD40684A8f21f4609FB811A7b", - "blockNumber": "38011", - "transactionIndex": "0x0", - "transactionHash": "0xc26df2bf1a733e9249372d61eb11bd8662d26c8129df76890b1beb2f6fa72628", - "data": { - "author": "0X91D2B4381A4CD5C7C0F27565A7D4B829844C8635", + "address": "0x1C197604587F046FD40684A8f21f4609FB811A7b", + "blockNumber": "38011", + "transactionIndex": "0x0", + "transactionHash": "0xc26df2bf1a733e9249372d61eb11bd8662d26c8129df76890b1beb2f6fa72628", + "data": { + "author": "0X91D2B4381A4CD5C7C0F27565A7D4B829844C8635", "namespace": "ns1", "uuids": "0xe19af8b390604051812d7597d19adfb9847d3bfd074249efb65d3fed15f5b0a6", - "batchHash": "0xd71eb138d74c229a388eb0e1abc03f4c7cbb21d4fc4b839fbf0ec73e4263f6be", - "payloadRef": "", + "batchHash": "0xd71eb138d74c229a388eb0e1abc03f4c7cbb21d4fc4b839fbf0ec73e4263f6be", + "payloadRef": "", "contexts": [ "0x68e4da79f805bca5b912bcda9c63d03e6e867108dabb9b944109aea541ef522a", "0x19b82093de5ce92a01e333048e877e2374354bf846dd034864ef6ffbd6438771" - ], - "timestamp": "1620576488" + ] }, - "subId": "sb-b5b97a4e-a317-4053-6400-1474650efcb5", - "signature": "BatchPin(address,uint256,string,bytes32,bytes32,string,bytes32[])", - "logIndex": "50" + "subId": "sb-b5b97a4e-a317-4053-6400-1474650efcb5", + "signature": "BatchPin(address,uint256,string,bytes32,bytes32,string,bytes32[])", + "logIndex": "50", + "timestamp": "1620576488" } ]`) @@ -663,7 +666,7 @@ func TestHandleMessageEmptyPayloadRef(t *testing.T) { ID: "sb-b5b97a4e-a317-4053-6400-1474650efcb5", } - em.On("BatchPinComplete", mock.Anything, "0x91d2b4381a4cd5c7c0f27565a7d4b829844c8635", mock.Anything, mock.Anything).Return(nil) + em.On("BatchPinComplete", mock.Anything, "0x91d2b4381a4cd5c7c0f27565a7d4b829844c8635", mock.Anything).Return(nil) var events []interface{} err := json.Unmarshal(data.Bytes(), &events) @@ -678,7 +681,6 @@ func TestHandleMessageEmptyPayloadRef(t *testing.T) { assert.Equal(t, "d71eb138d74c229a388eb0e1abc03f4c7cbb21d4fc4b839fbf0ec73e4263f6be", b.BatchHash.String()) assert.Empty(t, b.BatchPayloadRef) assert.Equal(t, "0x91d2b4381a4cd5c7c0f27565a7d4b829844c8635", em.Calls[0].Arguments[1]) - assert.Equal(t, "0xc26df2bf1a733e9249372d61eb11bd8662d26c8129df76890b1beb2f6fa72628", em.Calls[0].Arguments[2]) assert.Len(t, b.Contexts, 2) assert.Equal(t, "68e4da79f805bca5b912bcda9c63d03e6e867108dabb9b944109aea541ef522a", b.Contexts[0].String()) assert.Equal(t, "19b82093de5ce92a01e333048e877e2374354bf846dd034864ef6ffbd6438771", b.Contexts[1].String()) @@ -691,21 +693,21 @@ func TestHandleMessageBatchPinExit(t *testing.T) { data := fftypes.JSONAnyPtr(` [ { - "address": "0x1C197604587F046FD40684A8f21f4609FB811A7b", - "blockNumber": "38011", - "transactionIndex": "0x1", - "transactionHash": "0x0c50dff0893e795293189d9cc5ba0d63c4020d8758ace4a69d02c9d6d43cb695", - "data": { - "author": "0x91d2b4381a4cd5c7c0f27565a7d4b829844c8635", + "address": "0x1C197604587F046FD40684A8f21f4609FB811A7b", + "blockNumber": "38011", + "transactionIndex": "0x1", + "transactionHash": "0x0c50dff0893e795293189d9cc5ba0d63c4020d8758ace4a69d02c9d6d43cb695", + "data": { + "author": "0x91d2b4381a4cd5c7c0f27565a7d4b829844c8635", "namespace": "ns1", "uuids": "0xe19af8b390604051812d7597d19adfb9a04c7cc37d444c2ba3b054e21326697e", "batchHash": "0x9c19a93b6e85fee041f60f097121829e54cd4aa97ed070d1bc76147caf911fed", - "payloadRef": "Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", - "timestamp": "1620576488" + "payloadRef": "Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD" }, - "subId": "sb-b5b97a4e-a317-4053-6400-1474650efcb5", - "signature": "BatchPin(address,uint256,string,bytes32,bytes32,string,bytes32[])", - "logIndex": "51" + "subId": "sb-b5b97a4e-a317-4053-6400-1474650efcb5", + "signature": "BatchPin(address,uint256,string,bytes32,bytes32,string,bytes32[])", + "logIndex": "51", + "timestamp": "1620576488" } ]`) @@ -717,7 +719,7 @@ func TestHandleMessageBatchPinExit(t *testing.T) { ID: "sb-b5b97a4e-a317-4053-6400-1474650efcb5", } - em.On("BatchPinComplete", mock.Anything, "0x91d2b4381a4cd5c7c0f27565a7d4b829844c8635", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + em.On("BatchPinComplete", mock.Anything, "0x91d2b4381a4cd5c7c0f27565a7d4b829844c8635", mock.Anything).Return(fmt.Errorf("pop")) var events []interface{} err := json.Unmarshal(data.Bytes(), &events) @@ -748,6 +750,28 @@ func TestHandleMessageBatchPinEmpty(t *testing.T) { assert.Equal(t, 0, len(em.Calls)) } +func TestHandleMessageBatchMissingData(t *testing.T) { + em := &blockchainmocks.Callbacks{} + e := &Ethereum{callbacks: em} + e.initInfo.sub = &subscription{ + ID: "sb-b5b97a4e-a317-4053-6400-1474650efcb5", + } + + var events []interface{} + err := json.Unmarshal([]byte(` + [ + { + "subId": "sb-b5b97a4e-a317-4053-6400-1474650efcb5", + "signature": "BatchPin(address,uint256,string,bytes32,bytes32,string,bytes32[])", + "timestamp": "1620576488" + } + ]`), &events) + assert.NoError(t, err) + err = e.handleMessageBatch(context.Background(), events) + assert.NoError(t, err) + assert.Equal(t, 0, len(em.Calls)) +} + func TestHandleMessageBatchPinBadTransactionID(t *testing.T) { em := &blockchainmocks.Callbacks{} e := &Ethereum{callbacks: em} @@ -757,21 +781,21 @@ func TestHandleMessageBatchPinBadTransactionID(t *testing.T) { data := fftypes.JSONAnyPtr(`[{ "subId": "sb-b5b97a4e-a317-4053-6400-1474650efcb5", "signature": "BatchPin(address,uint256,string,bytes32,bytes32,string,bytes32[])", - "blockNumber": "38011", - "transactionIndex": "0x1", - "transactionHash": "0x0c50dff0893e795293189d9cc5ba0d63c4020d8758ace4a69d02c9d6d43cb695", + "blockNumber": "38011", + "transactionIndex": "0x1", + "transactionHash": "0x0c50dff0893e795293189d9cc5ba0d63c4020d8758ace4a69d02c9d6d43cb695", "data": { - "author": "0X91D2B4381A4CD5C7C0F27565A7D4B829844C8635", + "author": "0X91D2B4381A4CD5C7C0F27565A7D4B829844C8635", "namespace": "ns1", "uuids": "!good", "batchHash": "0xd71eb138d74c229a388eb0e1abc03f4c7cbb21d4fc4b839fbf0ec73e4263f6be", - "payloadRef": "0xeda586bd8f3c4bc1db5c4b5755113b9a9b4174abe28679fdbc219129400dd7ae", + "payloadRef": "0xeda586bd8f3c4bc1db5c4b5755113b9a9b4174abe28679fdbc219129400dd7ae", "contexts": [ "0xb41753f11522d4ef5c4a467972cf54744c04628ff84a1c994f1b288b2f6ec836", "0xc6c683a0fbe15e452e1ecc3751657446e2f645a8231e3ef9f3b4a8eae03c4136" - ], - "timestamp": "!1620576488" - } + ] + }, + "timestamp": "1620576488" }]`) var events []interface{} err := json.Unmarshal(data.Bytes(), &events) @@ -790,21 +814,21 @@ func TestHandleMessageBatchPinBadIDentity(t *testing.T) { data := fftypes.JSONAnyPtr(`[{ "subId": "sb-b5b97a4e-a317-4053-6400-1474650efcb5", "signature": "BatchPin(address,uint256,string,bytes32,bytes32,string,bytes32[])", - "blockNumber": "38011", - "transactionIndex": "0x1", - "transactionHash": "0x0c50dff0893e795293189d9cc5ba0d63c4020d8758ace4a69d02c9d6d43cb695", + "blockNumber": "38011", + "transactionIndex": "0x1", + "transactionHash": "0x0c50dff0893e795293189d9cc5ba0d63c4020d8758ace4a69d02c9d6d43cb695", "data": { - "author": "!good", + "author": "!good", "namespace": "ns1", "uuids": "0xe19af8b390604051812d7597d19adfb9847d3bfd074249efb65d3fed15f5b0a6", "batchHash": "0xd71eb138d74c229a388eb0e1abc03f4c7cbb21d4fc4b839fbf0ec73e4263f6be", - "payloadRef": "0xeda586bd8f3c4bc1db5c4b5755113b9a9b4174abe28679fdbc219129400dd7ae", + "payloadRef": "0xeda586bd8f3c4bc1db5c4b5755113b9a9b4174abe28679fdbc219129400dd7ae", "contexts": [ "0xb41753f11522d4ef5c4a467972cf54744c04628ff84a1c994f1b288b2f6ec836", "0xc6c683a0fbe15e452e1ecc3751657446e2f645a8231e3ef9f3b4a8eae03c4136" - ], - "timestamp": "1620576488" - } + ] + }, + "timestamp": "1620576488" }]`) var events []interface{} err := json.Unmarshal(data.Bytes(), &events) @@ -823,21 +847,21 @@ func TestHandleMessageBatchPinBadBatchHash(t *testing.T) { data := fftypes.JSONAnyPtr(`[{ "subId": "sb-b5b97a4e-a317-4053-6400-1474650efcb5", "signature": "BatchPin(address,uint256,string,bytes32,bytes32,string,bytes32[])", - "blockNumber": "38011", - "transactionIndex": "0x1", - "transactionHash": "0x0c50dff0893e795293189d9cc5ba0d63c4020d8758ace4a69d02c9d6d43cb695", + "blockNumber": "38011", + "transactionIndex": "0x1", + "transactionHash": "0x0c50dff0893e795293189d9cc5ba0d63c4020d8758ace4a69d02c9d6d43cb695", "data": { - "author": "0X91D2B4381A4CD5C7C0F27565A7D4B829844C8635", + "author": "0X91D2B4381A4CD5C7C0F27565A7D4B829844C8635", "namespace": "ns1", "uuids": "0xe19af8b390604051812d7597d19adfb9847d3bfd074249efb65d3fed15f5b0a6", "batchHash": "!good", - "payloadRef": "0xeda586bd8f3c4bc1db5c4b5755113b9a9b4174abe28679fdbc219129400dd7ae", + "payloadRef": "0xeda586bd8f3c4bc1db5c4b5755113b9a9b4174abe28679fdbc219129400dd7ae", "contexts": [ "0xb41753f11522d4ef5c4a467972cf54744c04628ff84a1c994f1b288b2f6ec836", "0xc6c683a0fbe15e452e1ecc3751657446e2f645a8231e3ef9f3b4a8eae03c4136" - ], - "timestamp": "1620576488" - } + ] + }, + "timestamp": "1620576488" }]`) var events []interface{} err := json.Unmarshal(data.Bytes(), &events) @@ -856,21 +880,21 @@ func TestHandleMessageBatchPinBadPin(t *testing.T) { data := fftypes.JSONAnyPtr(`[{ "subId": "sb-b5b97a4e-a317-4053-6400-1474650efcb5", "signature": "BatchPin(address,uint256,string,bytes32,bytes32,string,bytes32[])", - "blockNumber": "38011", - "transactionIndex": "0x1", - "transactionHash": "0x0c50dff0893e795293189d9cc5ba0d63c4020d8758ace4a69d02c9d6d43cb695", + "blockNumber": "38011", + "transactionIndex": "0x1", + "transactionHash": "0x0c50dff0893e795293189d9cc5ba0d63c4020d8758ace4a69d02c9d6d43cb695", "data": { - "author": "0X91D2B4381A4CD5C7C0F27565A7D4B829844C8635", + "author": "0X91D2B4381A4CD5C7C0F27565A7D4B829844C8635", "namespace": "ns1", "uuids": "0xe19af8b390604051812d7597d19adfb9847d3bfd074249efb65d3fed15f5b0a6", "batchHash": "0xd71eb138d74c229a388eb0e1abc03f4c7cbb21d4fc4b839fbf0ec73e4263f6be", - "payloadRef": "0xeda586bd8f3c4bc1db5c4b5755113b9a9b4174abe28679fdbc219129400dd7ae", + "payloadRef": "0xeda586bd8f3c4bc1db5c4b5755113b9a9b4174abe28679fdbc219129400dd7ae", "contexts": [ "0xb41753f11522d4ef5c4a467972cf54744c04628ff84a1c994f1b288b2f6ec836", "!good" - ], - "timestamp": "1620576488" - } + ] + }, + "timestamp": "1620576488" }]`) var events []interface{} err := json.Unmarshal(data.Bytes(), &events) @@ -937,26 +961,26 @@ func TestHandleReceiptTXSuccess(t *testing.T) { var reply fftypes.JSONObject operationID := fftypes.NewUUID() data := fftypes.JSONAnyPtr(`{ - "_id": "4373614c-e0f7-47b0-640e-7eacec417a9e", - "blockHash": "0xad269b2b43481e44500f583108e8d24bd841fb767c7f526772959d195b9c72d5", - "blockNumber": "209696", - "cumulativeGasUsed": "24655", - "from": "0x91d2b4381a4cd5c7c0f27565a7d4b829844c8635", - "gasUsed": "24655", - "headers": { - "id": "4603a151-f212-446e-5c15-0f36b57cecc7", - "requestId": "` + operationID.String() + `", - "requestOffset": "zzn4y4v4si-zzjjepe9x4-requests:0:12", - "timeElapsed": 3.966414429, - "timeReceived": "2021-05-28T20:54:27.481245697Z", - "type": "TransactionSuccess" + "_id": "4373614c-e0f7-47b0-640e-7eacec417a9e", + "blockHash": "0xad269b2b43481e44500f583108e8d24bd841fb767c7f526772959d195b9c72d5", + "blockNumber": "209696", + "cumulativeGasUsed": "24655", + "from": "0x91d2b4381a4cd5c7c0f27565a7d4b829844c8635", + "gasUsed": "24655", + "headers": { + "id": "4603a151-f212-446e-5c15-0f36b57cecc7", + "requestId": "` + operationID.String() + `", + "requestOffset": "zzn4y4v4si-zzjjepe9x4-requests:0:12", + "timeElapsed": 3.966414429, + "timeReceived": "2021-05-28T20:54:27.481245697Z", + "type": "TransactionSuccess" }, - "nonce": "0", - "receivedAt": 1622235271565, - "status": "1", - "to": "0xd3266a857285fb75eb7df37353b4a15c8bb828f5", - "transactionHash": "0x71a38acb7a5d4a970854f6d638ceb1fa10a4b59cbf4ed7674273a1a8dc8b36b8", - "transactionIndex": "0" + "nonce": "0", + "receivedAt": 1622235271565, + "status": "1", + "to": "0xd3266a857285fb75eb7df37353b4a15c8bb828f5", + "transactionHash": "0x71a38acb7a5d4a970854f6d638ceb1fa10a4b59cbf4ed7674273a1a8dc8b36b8", + "transactionIndex": "0" }`) em.On("BlockchainOpUpdate", @@ -1441,18 +1465,18 @@ func TestHandleMessageContractEvent(t *testing.T) { data := fftypes.JSONAnyPtr(` [ { - "address": "0x1C197604587F046FD40684A8f21f4609FB811A7b", - "blockNumber": "38011", - "transactionIndex": "0x0", - "transactionHash": "0xc26df2bf1a733e9249372d61eb11bd8662d26c8129df76890b1beb2f6fa72628", - "data": { - "from": "0x91D2B4381A4CD5C7C0F27565A7D4B829844C8635", + "address": "0x1C197604587F046FD40684A8f21f4609FB811A7b", + "blockNumber": "38011", + "transactionIndex": "0x0", + "transactionHash": "0xc26df2bf1a733e9249372d61eb11bd8662d26c8129df76890b1beb2f6fa72628", + "data": { + "from": "0x91D2B4381A4CD5C7C0F27565A7D4B829844C8635", "value": "1" }, - "subId": "sub2", - "signature": "Changed(address,uint256)", - "logIndex": "50", - "timestamp": "1640811383" + "subId": "sub2", + "signature": "Changed(address,uint256)", + "logIndex": "50", + "timestamp": "1640811383" } ]`) @@ -1474,13 +1498,13 @@ func TestHandleMessageContractEvent(t *testing.T) { ev := em.Calls[0].Arguments[0].(*blockchain.ContractEvent) assert.Equal(t, "sub2", ev.Subscription) - assert.Equal(t, "Changed", ev.Name) + assert.Equal(t, "Changed", ev.Event.Name) outputs := fftypes.JSONObject{ "from": "0x91D2B4381A4CD5C7C0F27565A7D4B829844C8635", "value": "1", } - assert.Equal(t, outputs, ev.Outputs) + assert.Equal(t, outputs, ev.Event.Output) info := fftypes.JSONObject{ "address": "0x1C197604587F046FD40684A8f21f4609FB811A7b", @@ -1492,7 +1516,7 @@ func TestHandleMessageContractEvent(t *testing.T) { "transactionIndex": "0x0", "timestamp": "1640811383", } - assert.Equal(t, info, ev.Info) + assert.Equal(t, info, ev.Event.Info) em.AssertExpectations(t) } @@ -1501,17 +1525,17 @@ func TestHandleMessageContractEventNoTimestamp(t *testing.T) { data := fftypes.JSONAnyPtr(` [ { - "address": "0x1C197604587F046FD40684A8f21f4609FB811A7b", - "blockNumber": "38011", - "transactionIndex": "0x0", - "transactionHash": "0xc26df2bf1a733e9249372d61eb11bd8662d26c8129df76890b1beb2f6fa72628", - "data": { - "from": "0x91D2B4381A4CD5C7C0F27565A7D4B829844C8635", + "address": "0x1C197604587F046FD40684A8f21f4609FB811A7b", + "blockNumber": "38011", + "transactionIndex": "0x0", + "transactionHash": "0xc26df2bf1a733e9249372d61eb11bd8662d26c8129df76890b1beb2f6fa72628", + "data": { + "from": "0x91D2B4381A4CD5C7C0F27565A7D4B829844C8635", "value": "1" }, - "subId": "sub2", - "signature": "Changed(address,uint256)", - "logIndex": "50" + "subId": "sub2", + "signature": "Changed(address,uint256)", + "logIndex": "50" } ]`) @@ -1536,18 +1560,18 @@ func TestHandleMessageContractEventError(t *testing.T) { data := fftypes.JSONAnyPtr(` [ { - "address": "0x1C197604587F046FD40684A8f21f4609FB811A7b", - "blockNumber": "38011", - "transactionIndex": "0x0", - "transactionHash": "0xc26df2bf1a733e9249372d61eb11bd8662d26c8129df76890b1beb2f6fa72628", - "data": { - "from": "0x91D2B4381A4CD5C7C0F27565A7D4B829844C8635", + "address": "0x1C197604587F046FD40684A8f21f4609FB811A7b", + "blockNumber": "38011", + "transactionIndex": "0x0", + "transactionHash": "0xc26df2bf1a733e9249372d61eb11bd8662d26c8129df76890b1beb2f6fa72628", + "data": { + "from": "0x91D2B4381A4CD5C7C0F27565A7D4B829844C8635", "value": "1" }, - "subId": "sub2", - "signature": "Changed(address,uint256)", - "logIndex": "50", - "timestamp": "1640811383" + "subId": "sub2", + "signature": "Changed(address,uint256)", + "logIndex": "50", + "timestamp": "1640811383" } ]`) diff --git a/internal/blockchain/fabric/fabric.go b/internal/blockchain/fabric/fabric.go index bcdf3e6320..38f9f8803f 100644 --- a/internal/blockchain/fabric/fabric.go +++ b/internal/blockchain/fabric/fabric.go @@ -241,7 +241,7 @@ func (f *Fabric) afterConnect(ctx context.Context, w wsclient.WSClient) error { return err } -func (f *Fabric) decodeJSONPayload(ctx context.Context, payloadString string) *fftypes.JSONObject { +func decodeJSONPayload(ctx context.Context, payloadString string) *fftypes.JSONObject { bytes, err := base64.StdEncoding.DecodeString(payloadString) if err != nil { log.L(ctx).Errorf("BatchPin event is not valid - bad payload content: %s", payloadString) @@ -258,7 +258,7 @@ func (f *Fabric) decodeJSONPayload(ctx context.Context, payloadString string) *f func (f *Fabric) handleBatchPinEvent(ctx context.Context, msgJSON fftypes.JSONObject) (err error) { payloadString := msgJSON.GetString("payload") - payload := f.decodeJSONPayload(ctx, payloadString) + payload := decodeJSONPayload(ctx, payloadString) if payload == nil { return nil // move on } @@ -299,6 +299,7 @@ func (f *Fabric) handleBatchPinEvent(ctx context.Context, msgJSON fftypes.JSONOb contexts[i] = &hash } + delete(msgJSON, "payload") batch := &blockchain.BatchPin{ Namespace: ns, TransactionID: &txnID, @@ -306,29 +307,42 @@ func (f *Fabric) handleBatchPinEvent(ctx context.Context, msgJSON fftypes.JSONOb BatchHash: &batchHash, BatchPayloadRef: sPayloadRef, Contexts: contexts, + Event: blockchain.Event{ + Source: f.Name(), + Name: "BatchPin", + ProtocolID: sTransactionHash, + Output: *payload, + Info: msgJSON, + }, } // If there's an error dispatching the event, we must return the error and shutdown - return f.callbacks.BatchPinComplete(batch, signer, sTransactionHash, msgJSON) + return f.callbacks.BatchPinComplete(batch, signer) } func (f *Fabric) handleContractEvent(ctx context.Context, msgJSON fftypes.JSONObject) (err error) { payloadString := msgJSON.GetString("payload") - payload := f.decodeJSONPayload(ctx, payloadString) + payload := decodeJSONPayload(ctx, payloadString) if payload == nil { return nil // move on } delete(msgJSON, "payload") + sTransactionHash := msgJSON.GetString("transactionId") sub := msgJSON.GetString("subId") name := msgJSON.GetString("eventName") event := &blockchain.ContractEvent{ Subscription: sub, - Name: name, - Outputs: *payload, - Info: msgJSON, + Event: blockchain.Event{ + Source: f.Name(), + Name: name, + ProtocolID: sTransactionHash, + Output: *payload, + Info: msgJSON, + }, } + return f.callbacks.ContractEvent(event) } diff --git a/internal/blockchain/fabric/fabric_test.go b/internal/blockchain/fabric/fabric_test.go index 72578f59b3..6bcfd2934d 100644 --- a/internal/blockchain/fabric/fabric_test.go +++ b/internal/blockchain/fabric/fabric_test.go @@ -613,7 +613,6 @@ func TestHandleMessageBatchPinOK(t *testing.T) { assert.Equal(t, "d71eb138d74c229a388eb0e1abc03f4c7cbb21d4fc4b839fbf0ec73e4263f6be", b.BatchHash.String()) assert.Equal(t, "Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", b.BatchPayloadRef) assert.Equal(t, "u0vgwu9s00-x509::CN=user2,OU=client::CN=fabric-ca-server", em.Calls[0].Arguments[1]) - assert.Equal(t, "ce79343000e851a0c742f63a733ce19a5f8b9ce1c719b6cecd14f01bcf81fff2", em.Calls[0].Arguments[2]) assert.Len(t, b.Contexts, 2) assert.Equal(t, "68e4da79f805bca5b912bcda9c63d03e6e867108dabb9b944109aea541ef522a", b.Contexts[0].String()) assert.Equal(t, "19b82093de5ce92a01e333048e877e2374354bf846dd034864ef6ffbd6438771", b.Contexts[1].String()) @@ -658,7 +657,6 @@ func TestHandleMessageEmptyPayloadRef(t *testing.T) { assert.Equal(t, "d71eb138d74c229a388eb0e1abc03f4c7cbb21d4fc4b839fbf0ec73e4263f6be", b.BatchHash.String()) assert.Empty(t, b.BatchPayloadRef) assert.Equal(t, "u0vgwu9s00-x509::CN=user2,OU=client::CN=fabric-ca-server", em.Calls[0].Arguments[1]) - assert.Equal(t, "ce79343000e851a0c742f63a733ce19a5f8b9ce1c719b6cecd14f01bcf81fff2", em.Calls[0].Arguments[2]) assert.Len(t, b.Contexts, 2) assert.Equal(t, "68e4da79f805bca5b912bcda9c63d03e6e867108dabb9b944109aea541ef522a", b.Contexts[0].String()) assert.Equal(t, "19b82093de5ce92a01e333048e877e2374354bf846dd034864ef6ffbd6438771", b.Contexts[1].String()) @@ -1220,7 +1218,7 @@ func TestHandleMessageContractEvent(t *testing.T) { ev := em.Calls[0].Arguments[0].(*blockchain.ContractEvent) assert.Equal(t, "sb-cb37cc07-e873-4f58-44ab-55add6bba320", ev.Subscription) - assert.Equal(t, "AssetCreated", ev.Name) + assert.Equal(t, "AssetCreated", ev.Event.Name) outputs := fftypes.JSONObject{ "AppraisedValue": float64(10), @@ -1229,7 +1227,7 @@ func TestHandleMessageContractEvent(t *testing.T) { "Owner": "me", "Size": float64(3), } - assert.Equal(t, outputs, ev.Outputs) + assert.Equal(t, outputs, ev.Event.Output) info := fftypes.JSONObject{ "blockNumber": float64(10), @@ -1238,7 +1236,7 @@ func TestHandleMessageContractEvent(t *testing.T) { "subId": "sb-cb37cc07-e873-4f58-44ab-55add6bba320", "transactionId": "4763a0c50e3bba7cef1a7ba35dd3f9f3426bb04d0156f326e84ec99387c4746d", } - assert.Equal(t, info, ev.Info) + assert.Equal(t, info, ev.Event.Info) em.AssertExpectations(t) } diff --git a/internal/contracts/manager.go b/internal/contracts/manager.go index 56db033cf4..ad3d8d6cf9 100644 --- a/internal/contracts/manager.go +++ b/internal/contracts/manager.go @@ -50,8 +50,8 @@ type Manager interface { GetContractSubscriptionByNameOrID(ctx context.Context, ns, nameOrID string) (*fftypes.ContractSubscription, error) GetContractSubscriptions(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.ContractSubscription, *database.FilterResult, error) DeleteContractSubscriptionByNameOrID(ctx context.Context, ns, nameOrID string) error - GetContractEventByID(ctx context.Context, id *fftypes.UUID) (*fftypes.ContractEvent, error) - GetContractEvents(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.ContractEvent, *database.FilterResult, error) + GetContractEventByID(ctx context.Context, id *fftypes.UUID) (*fftypes.BlockchainEvent, error) + GetContractEvents(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.BlockchainEvent, *database.FilterResult, error) } type contractManager struct { @@ -512,10 +512,10 @@ func (cm *contractManager) SubscribeContractAPI(ctx context.Context, ns, apiName return cm.SubscribeContract(ctx, ns, eventPath, req) } -func (cm *contractManager) GetContractEventByID(ctx context.Context, id *fftypes.UUID) (*fftypes.ContractEvent, error) { - return cm.database.GetContractEventByID(ctx, id) +func (cm *contractManager) GetContractEventByID(ctx context.Context, id *fftypes.UUID) (*fftypes.BlockchainEvent, error) { + return cm.database.GetBlockchainEventByID(ctx, id) } -func (cm *contractManager) GetContractEvents(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.ContractEvent, *database.FilterResult, error) { - return cm.database.GetContractEvents(ctx, cm.scopeNS(ns, filter)) +func (cm *contractManager) GetContractEvents(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.BlockchainEvent, *database.FilterResult, error) { + return cm.database.GetBlockchainEvents(ctx, cm.scopeNS(ns, filter)) } diff --git a/internal/contracts/manager_test.go b/internal/contracts/manager_test.go index f3cd4c577b..683fb834ad 100644 --- a/internal/contracts/manager_test.go +++ b/internal/contracts/manager_test.go @@ -1384,7 +1384,7 @@ func TestGetContractEventByID(t *testing.T) { mdi := cm.database.(*databasemocks.Plugin) id := fftypes.NewUUID() - mdi.On("GetContractEventByID", context.Background(), id).Return(&fftypes.ContractEvent{}, nil) + mdi.On("GetBlockchainEventByID", context.Background(), id).Return(&fftypes.BlockchainEvent{}, nil) _, err := cm.GetContractEventByID(context.Background(), id) assert.NoError(t, err) @@ -1394,7 +1394,7 @@ func TestGetContractEvents(t *testing.T) { cm := newTestContractManager() mdi := cm.database.(*databasemocks.Plugin) - mdi.On("GetContractEvents", context.Background(), mock.Anything).Return(nil, nil, nil) + mdi.On("GetBlockchainEvents", context.Background(), mock.Anything).Return(nil, nil, nil) f := database.ContractSubscriptionQueryFactory.NewFilter(context.Background()) _, _, err := cm.GetContractEvents(context.Background(), "ns", f.And()) diff --git a/internal/database/sqlcommon/batch_sql.go b/internal/database/sqlcommon/batch_sql.go index 8acb6da7d9..af25ee9174 100644 --- a/internal/database/sqlcommon/batch_sql.go +++ b/internal/database/sqlcommon/batch_sql.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -45,12 +45,12 @@ var ( "node_id", } batchFilterFieldMap = map[string]string{ - "type": "btype", - "payloadref": "payload_ref", - "transaction.type": "tx_type", - "transaction.id": "tx_id", - "group": "group_hash", - "node": "node_id", + "type": "btype", + "payloadref": "payload_ref", + "tx.type": "tx_type", + "tx.id": "tx_id", + "group": "group_hash", + "node": "node_id", } ) diff --git a/internal/database/sqlcommon/contractevents_sql.go b/internal/database/sqlcommon/blockchainevents_sql.go similarity index 53% rename from internal/database/sqlcommon/contractevents_sql.go rename to internal/database/sqlcommon/blockchainevents_sql.go index d831c55480..4740a77f8a 100644 --- a/internal/database/sqlcommon/contractevents_sql.go +++ b/internal/database/sqlcommon/blockchainevents_sql.go @@ -28,21 +28,28 @@ import ( ) var ( - contractEventColumns = []string{ + blockchainEventColumns = []string{ "id", + "source", "namespace", - "subscription_id", "name", - "outputs", + "protocol_id", + "subscription_id", + "output", "info", "timestamp", + "tx_type", + "tx_id", } - contractEventFilterFieldMap = map[string]string{ + blockchainEventFilterFieldMap = map[string]string{ + "protocolid": "protocol_id", "subscription": "subscription_id", + "tx.type": "tx_type", + "tx.id": "tx_id", } ) -func (s *SQLCommon) InsertContractEvent(ctx context.Context, event *fftypes.ContractEvent) (err error) { +func (s *SQLCommon) InsertBlockchainEvent(ctx context.Context, event *fftypes.BlockchainEvent) (err error) { ctx, tx, autoCommit, err := s.beginOrUseTx(ctx) if err != nil { return err @@ -50,19 +57,23 @@ func (s *SQLCommon) InsertContractEvent(ctx context.Context, event *fftypes.Cont defer s.rollbackTx(ctx, tx, autoCommit) if event.Sequence, err = s.insertTx(ctx, tx, - sq.Insert("contractevents"). - Columns(contractEventColumns...). + sq.Insert("blockchainevents"). + Columns(blockchainEventColumns...). Values( event.ID, + event.Source, event.Namespace, - event.Subscription, event.Name, - event.Outputs, + event.ProtocolID, + event.Subscription, + event.Output, event.Info, event.Timestamp, + event.TX.Type, + event.TX.ID, ), func() { - s.callbacks.OrderedUUIDCollectionNSEvent(database.CollectionContractEvents, fftypes.ChangeEventTypeCreated, event.Namespace, event.ID, event.Sequence) + s.callbacks.OrderedUUIDCollectionNSEvent(database.CollectionBlockchainEvents, fftypes.ChangeEventTypeCreated, event.Namespace, event.ID, event.Sequence) }, ); err != nil { return err @@ -71,32 +82,36 @@ func (s *SQLCommon) InsertContractEvent(ctx context.Context, event *fftypes.Cont return s.commitTx(ctx, tx, autoCommit) } -func (s *SQLCommon) contractEventResult(ctx context.Context, row *sql.Rows) (*fftypes.ContractEvent, error) { - var event fftypes.ContractEvent +func (s *SQLCommon) blockchainEventResult(ctx context.Context, row *sql.Rows) (*fftypes.BlockchainEvent, error) { + var event fftypes.BlockchainEvent err := row.Scan( &event.ID, + &event.Source, &event.Namespace, - &event.Subscription, &event.Name, - &event.Outputs, + &event.ProtocolID, + &event.Subscription, + &event.Output, &event.Info, &event.Timestamp, + &event.TX.Type, + &event.TX.ID, // Must be added to the list of columns in all selects &event.Sequence, ) if err != nil { - return nil, i18n.WrapError(ctx, err, i18n.MsgDBReadErr, "contractevents") + return nil, i18n.WrapError(ctx, err, i18n.MsgDBReadErr, "blockchainevents") } return &event, nil } -func (s *SQLCommon) getContractEventPred(ctx context.Context, desc string, pred interface{}) (*fftypes.ContractEvent, error) { - cols := append([]string{}, contractEventColumns...) +func (s *SQLCommon) getBlockchainEventPred(ctx context.Context, desc string, pred interface{}) (*fftypes.BlockchainEvent, error) { + cols := append([]string{}, blockchainEventColumns...) cols = append(cols, sequenceColumn) rows, _, err := s.query(ctx, sq.Select(cols...). - From("contractevents"). + From("blockchainevents"). Where(pred), ) if err != nil { @@ -105,11 +120,11 @@ func (s *SQLCommon) getContractEventPred(ctx context.Context, desc string, pred defer rows.Close() if !rows.Next() { - log.L(ctx).Debugf("Contract event '%s' not found", desc) + log.L(ctx).Debugf("Blockchain event '%s' not found", desc) return nil, nil } - event, err := s.contractEventResult(ctx, rows) + event, err := s.blockchainEventResult(ctx, rows) if err != nil { return nil, err } @@ -117,17 +132,17 @@ func (s *SQLCommon) getContractEventPred(ctx context.Context, desc string, pred return event, nil } -func (s *SQLCommon) GetContractEventByID(ctx context.Context, id *fftypes.UUID) (*fftypes.ContractEvent, error) { - return s.getContractEventPred(ctx, id.String(), sq.Eq{"id": id}) +func (s *SQLCommon) GetBlockchainEventByID(ctx context.Context, id *fftypes.UUID) (*fftypes.BlockchainEvent, error) { + return s.getBlockchainEventPred(ctx, id.String(), sq.Eq{"id": id}) } -func (s *SQLCommon) GetContractEvents(ctx context.Context, filter database.Filter) ([]*fftypes.ContractEvent, *database.FilterResult, error) { - cols := append([]string{}, contractEventColumns...) +func (s *SQLCommon) GetBlockchainEvents(ctx context.Context, filter database.Filter) ([]*fftypes.BlockchainEvent, *database.FilterResult, error) { + cols := append([]string{}, blockchainEventColumns...) cols = append(cols, sequenceColumn) query, fop, fi, err := s.filterSelect(ctx, "", - sq.Select(cols...).From("contractevents"), - filter, contractEventFilterFieldMap, []interface{}{"sequence"}) + sq.Select(cols...).From("blockchainevents"), + filter, blockchainEventFilterFieldMap, []interface{}{"sequence"}) if err != nil { return nil, nil, err } @@ -138,14 +153,14 @@ func (s *SQLCommon) GetContractEvents(ctx context.Context, filter database.Filte } defer rows.Close() - events := []*fftypes.ContractEvent{} + events := []*fftypes.BlockchainEvent{} for rows.Next() { - event, err := s.contractEventResult(ctx, rows) + event, err := s.blockchainEventResult(ctx, rows) if err != nil { return nil, nil, err } events = append(events, event) } - return events, s.queryRes(ctx, tx, "contractevents", fop, fi), err + return events, s.queryRes(ctx, tx, "blockchainevents", fop, fi), err } diff --git a/internal/database/sqlcommon/contractevents_sql_test.go b/internal/database/sqlcommon/blockchainevents_sql_test.go similarity index 65% rename from internal/database/sqlcommon/contractevents_sql_test.go rename to internal/database/sqlcommon/blockchainevents_sql_test.go index c4818d96d7..e68a121c0c 100644 --- a/internal/database/sqlcommon/contractevents_sql_test.go +++ b/internal/database/sqlcommon/blockchainevents_sql_test.go @@ -28,36 +28,37 @@ import ( "github.com/stretchr/testify/assert" ) -func TestContractEventsE2EWithDB(t *testing.T) { +func TestBlockchainEventsE2EWithDB(t *testing.T) { s, cleanup := newSQLiteTestProvider(t) defer cleanup() ctx := context.Background() // Create a new contract event entry - event := &fftypes.ContractEvent{ + event := &fftypes.BlockchainEvent{ ID: fftypes.NewUUID(), Namespace: "ns", Subscription: fftypes.NewUUID(), Name: "Changed", - Outputs: fftypes.JSONObject{"value": 1}, + ProtocolID: "tx1", + Output: fftypes.JSONObject{"value": 1}, Info: fftypes.JSONObject{"blockNumber": 1}, Timestamp: fftypes.Now(), } - s.callbacks.On("OrderedUUIDCollectionNSEvent", database.CollectionContractEvents, fftypes.ChangeEventTypeCreated, "ns", event.ID, int64(1)).Return() + s.callbacks.On("OrderedUUIDCollectionNSEvent", database.CollectionBlockchainEvents, fftypes.ChangeEventTypeCreated, "ns", event.ID, int64(1)).Return() - err := s.InsertContractEvent(ctx, event) + err := s.InsertBlockchainEvent(ctx, event) assert.NotNil(t, event.Timestamp) assert.NoError(t, err) eventJson, _ := json.Marshal(&event) // Query back the event (by query filter) - fb := database.ContractEventQueryFactory.NewFilter(ctx) + fb := database.BlockchainEventQueryFactory.NewFilter(ctx) filter := fb.And( fb.Eq("name", "Changed"), fb.Eq("subscription", event.Subscription), ) - events, res, err := s.GetContractEvents(ctx, filter.Count(true)) + events, res, err := s.GetBlockchainEvents(ctx, filter.Count(true)) assert.NoError(t, err) assert.Equal(t, 1, len(events)) assert.Equal(t, int64(1), *res.TotalCount) @@ -65,86 +66,86 @@ func TestContractEventsE2EWithDB(t *testing.T) { assert.Equal(t, string(eventJson), string(eventReadJson)) // Query back the event (by ID) - eventRead, err := s.GetContractEventByID(ctx, event.ID) + eventRead, err := s.GetBlockchainEventByID(ctx, event.ID) assert.NoError(t, err) eventReadJson, _ = json.Marshal(eventRead) assert.Equal(t, string(eventJson), string(eventReadJson)) } -func TestInsertContractEventFailBegin(t *testing.T) { +func TestInsertBlockchainEventFailBegin(t *testing.T) { s, mock := newMockProvider().init() mock.ExpectBegin().WillReturnError(fmt.Errorf("pop")) - err := s.InsertContractEvent(context.Background(), &fftypes.ContractEvent{}) + err := s.InsertBlockchainEvent(context.Background(), &fftypes.BlockchainEvent{}) assert.Regexp(t, "FF10114", err) assert.NoError(t, mock.ExpectationsWereMet()) } -func TestInsertContractEventFailInsert(t *testing.T) { +func TestInsertBlockchainEventFailInsert(t *testing.T) { s, mock := newMockProvider().init() mock.ExpectBegin() mock.ExpectExec("INSERT .*").WillReturnError(fmt.Errorf("pop")) mock.ExpectRollback() - err := s.InsertContractEvent(context.Background(), &fftypes.ContractEvent{}) + err := s.InsertBlockchainEvent(context.Background(), &fftypes.BlockchainEvent{}) assert.Regexp(t, "FF10116", err) assert.NoError(t, mock.ExpectationsWereMet()) } -func TestInsertContractEventFailCommit(t *testing.T) { +func TestInsertBlockchainEventFailCommit(t *testing.T) { s, mock := newMockProvider().init() mock.ExpectBegin() mock.ExpectExec("INSERT .*").WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit().WillReturnError(fmt.Errorf("pop")) - err := s.InsertContractEvent(context.Background(), &fftypes.ContractEvent{}) + err := s.InsertBlockchainEvent(context.Background(), &fftypes.BlockchainEvent{}) assert.Regexp(t, "FF10119", err) assert.NoError(t, mock.ExpectationsWereMet()) } -func TestGetContractEventByIDSelectFail(t *testing.T) { +func TestGetBlockchainEventByIDSelectFail(t *testing.T) { s, mock := newMockProvider().init() mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop")) - _, err := s.GetContractEventByID(context.Background(), fftypes.NewUUID()) + _, err := s.GetBlockchainEventByID(context.Background(), fftypes.NewUUID()) assert.Regexp(t, "FF10115", err) assert.NoError(t, mock.ExpectationsWereMet()) } -func TestGetContractEventByIDNotFound(t *testing.T) { +func TestGetBlockchainEventByIDNotFound(t *testing.T) { s, mock := newMockProvider().init() mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"})) - msg, err := s.GetContractEventByID(context.Background(), fftypes.NewUUID()) + msg, err := s.GetBlockchainEventByID(context.Background(), fftypes.NewUUID()) assert.NoError(t, err) assert.Nil(t, msg) assert.NoError(t, mock.ExpectationsWereMet()) } -func TestGetContractEventByIDScanFail(t *testing.T) { +func TestGetBlockchainEventByIDScanFail(t *testing.T) { s, mock := newMockProvider().init() mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow("only one")) - _, err := s.GetContractEventByID(context.Background(), fftypes.NewUUID()) + _, err := s.GetBlockchainEventByID(context.Background(), fftypes.NewUUID()) assert.Regexp(t, "FF10121", err) assert.NoError(t, mock.ExpectationsWereMet()) } -func TestGetContractEventsQueryFail(t *testing.T) { +func TestGetBlockchainEventsQueryFail(t *testing.T) { s, mock := newMockProvider().init() mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop")) - f := database.ContractEventQueryFactory.NewFilter(context.Background()).Eq("id", "") - _, _, err := s.GetContractEvents(context.Background(), f) + f := database.BlockchainEventQueryFactory.NewFilter(context.Background()).Eq("id", "") + _, _, err := s.GetBlockchainEvents(context.Background(), f) assert.Regexp(t, "FF10115", err) assert.NoError(t, mock.ExpectationsWereMet()) } -func TestGetContractEventsBuildQueryFail(t *testing.T) { +func TestGetBlockchainEventsBuildQueryFail(t *testing.T) { s, _ := newMockProvider().init() - f := database.ContractEventQueryFactory.NewFilter(context.Background()).Eq("id", map[bool]bool{true: false}) - _, _, err := s.GetContractEvents(context.Background(), f) + f := database.BlockchainEventQueryFactory.NewFilter(context.Background()).Eq("id", map[bool]bool{true: false}) + _, _, err := s.GetBlockchainEvents(context.Background(), f) assert.Regexp(t, "FF10149.*id", err) } -func TestGetContractEventsScanFail(t *testing.T) { +func TestGetBlockchainEventsScanFail(t *testing.T) { s, mock := newMockProvider().init() mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow("only one")) - f := database.ContractEventQueryFactory.NewFilter(context.Background()).Eq("id", "") - _, _, err := s.GetContractEvents(context.Background(), f) + f := database.BlockchainEventQueryFactory.NewFilter(context.Background()).Eq("id", "") + _, _, err := s.GetBlockchainEvents(context.Background(), f) assert.Regexp(t, "FF10121", err) assert.NoError(t, mock.ExpectationsWereMet()) } diff --git a/internal/database/sqlcommon/tokentransfer_sql.go b/internal/database/sqlcommon/tokentransfer_sql.go index 6f2724f63a..53e6b2d001 100644 --- a/internal/database/sqlcommon/tokentransfer_sql.go +++ b/internal/database/sqlcommon/tokentransfer_sql.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -43,21 +43,19 @@ var ( "protocol_id", "message_id", "message_hash", - "tx_type", - "tx_id", + "blockchain_event", "created", } tokenTransferFilterFieldMap = map[string]string{ - "localid": "local_id", - "pool": "pool_id", - "tokenindex": "token_index", - "from": "from_key", - "to": "to_key", - "protocolid": "protocol_id", - "message": "message_id", - "messagehash": "message_hash", - "transaction.type": "tx_type", - "transaction.id": "tx_id", + "localid": "local_id", + "pool": "pool_id", + "tokenindex": "token_index", + "from": "from_key", + "to": "to_key", + "protocolid": "protocol_id", + "message": "message_id", + "messagehash": "message_hash", + "blockchainevent": "blockchain_event", } ) @@ -95,8 +93,7 @@ func (s *SQLCommon) UpsertTokenTransfer(ctx context.Context, transfer *fftypes.T Set("amount", transfer.Amount). Set("message_id", transfer.Message). Set("message_hash", transfer.MessageHash). - Set("tx_type", transfer.TX.Type). - Set("tx_id", transfer.TX.ID). + Set("blockchain_event", transfer.BlockchainEvent). Where(sq.Eq{"protocol_id": transfer.ProtocolID}), func() { s.callbacks.UUIDCollectionEvent(database.CollectionTokenTransfers, fftypes.ChangeEventTypeUpdated, transfer.LocalID) @@ -124,8 +121,7 @@ func (s *SQLCommon) UpsertTokenTransfer(ctx context.Context, transfer *fftypes.T transfer.ProtocolID, transfer.Message, transfer.MessageHash, - transfer.TX.Type, - transfer.TX.ID, + transfer.BlockchainEvent, transfer.Created, ), func() { @@ -156,8 +152,7 @@ func (s *SQLCommon) tokenTransferResult(ctx context.Context, row *sql.Rows) (*ff &transfer.ProtocolID, &transfer.Message, &transfer.MessageHash, - &transfer.TX.Type, - &transfer.TX.ID, + &transfer.BlockchainEvent, &transfer.Created, ) if err != nil { diff --git a/internal/database/sqlcommon/tokentransfer_sql_test.go b/internal/database/sqlcommon/tokentransfer_sql_test.go index ca1f4716ab..211b221cde 100644 --- a/internal/database/sqlcommon/tokentransfer_sql_test.go +++ b/internal/database/sqlcommon/tokentransfer_sql_test.go @@ -36,22 +36,19 @@ func TestTokenTransferE2EWithDB(t *testing.T) { // Create a new token transfer entry transfer := &fftypes.TokenTransfer{ - LocalID: fftypes.NewUUID(), - Type: fftypes.TokenTransferTypeTransfer, - Pool: fftypes.NewUUID(), - TokenIndex: "1", - URI: "firefly://token/1", - Connector: "erc1155", - Namespace: "ns1", - From: "0x01", - To: "0x02", - ProtocolID: "12345", - Message: fftypes.NewUUID(), - MessageHash: fftypes.NewRandB32(), - TX: fftypes.TransactionRef{ - Type: fftypes.TransactionTypeTokenTransfer, - ID: fftypes.NewUUID(), - }, + LocalID: fftypes.NewUUID(), + Type: fftypes.TokenTransferTypeTransfer, + Pool: fftypes.NewUUID(), + TokenIndex: "1", + URI: "firefly://token/1", + Connector: "erc1155", + Namespace: "ns1", + From: "0x01", + To: "0x02", + ProtocolID: "12345", + Message: fftypes.NewUUID(), + MessageHash: fftypes.NewRandB32(), + BlockchainEvent: fftypes.NewUUID(), } transfer.Amount.Int().SetInt64(10) diff --git a/internal/database/sqlcommon/transaction_sql.go b/internal/database/sqlcommon/transaction_sql.go index 8c227fcae9..c9aa443f8b 100644 --- a/internal/database/sqlcommon/transaction_sql.go +++ b/internal/database/sqlcommon/transaction_sql.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -32,31 +32,24 @@ var ( "id", "ttype", "namespace", - "ref", - "signer", - "hash", "created", - "protocol_id", "status", - "info", } transactionFilterFieldMap = map[string]string{ - "type": "ttype", - "protocolid": "protocol_id", - "reference": "ref", + "type": "ttype", } ) -func (s *SQLCommon) UpsertTransaction(ctx context.Context, transaction *fftypes.Transaction, allowHashUpdate bool) (err error) { +func (s *SQLCommon) UpsertTransaction(ctx context.Context, transaction *fftypes.Transaction) (err error) { ctx, tx, autoCommit, err := s.beginOrUseTx(ctx) if err != nil { return err } defer s.rollbackTx(ctx, tx, autoCommit) - // Do a select within the transaction to detemine if the UUID already exists + // Do a select within the transaction to determine if the UUID already exists transactionRows, _, err := s.queryTx(ctx, tx, - sq.Select("hash"). + sq.Select("seq"). From("transactions"). Where(sq.Eq{"id": transaction.ID}), ) @@ -64,58 +57,37 @@ func (s *SQLCommon) UpsertTransaction(ctx context.Context, transaction *fftypes. return err } existing := transactionRows.Next() - - if existing && !allowHashUpdate { - var hash *fftypes.Bytes32 - _ = transactionRows.Scan(&hash) - if !fftypes.SafeHashCompare(hash, transaction.Hash) { - transactionRows.Close() - log.L(ctx).Errorf("Existing=%s New=%s", hash, transaction.Hash) - return database.HashMismatch - } - } transactionRows.Close() if existing { - // Update the transaction if _, err = s.updateTx(ctx, tx, sq.Update("transactions"). - Set("ttype", string(transaction.Subject.Type)). - Set("namespace", transaction.Subject.Namespace). - Set("ref", transaction.Subject.Reference). - Set("signer", transaction.Subject.Signer). - Set("hash", transaction.Hash). - Set("created", transaction.Created). - Set("protocol_id", transaction.ProtocolID). + Set("ttype", string(transaction.Type)). + Set("namespace", transaction.Namespace). Set("status", transaction.Status). - Set("info", transaction.Info). Where(sq.Eq{"id": transaction.ID}), func() { - s.callbacks.UUIDCollectionNSEvent(database.CollectionTransactions, fftypes.ChangeEventTypeUpdated, transaction.Subject.Namespace, transaction.ID) + s.callbacks.UUIDCollectionNSEvent(database.CollectionTransactions, fftypes.ChangeEventTypeUpdated, transaction.Namespace, transaction.ID) }, ); err != nil { return err } } else { - + // Insert a transaction + transaction.Created = fftypes.Now() if _, err = s.insertTx(ctx, tx, sq.Insert("transactions"). Columns(transactionColumns...). Values( transaction.ID, - string(transaction.Subject.Type), - transaction.Subject.Namespace, - transaction.Subject.Reference, - transaction.Subject.Signer, - transaction.Hash, + string(transaction.Type), + transaction.Namespace, transaction.Created, - transaction.ProtocolID, transaction.Status, - transaction.Info, ), func() { - s.callbacks.UUIDCollectionNSEvent(database.CollectionTransactions, fftypes.ChangeEventTypeCreated, transaction.Subject.Namespace, transaction.ID) + s.callbacks.UUIDCollectionNSEvent(database.CollectionTransactions, fftypes.ChangeEventTypeCreated, transaction.Namespace, transaction.ID) }, ); err != nil { return err @@ -129,15 +101,10 @@ func (s *SQLCommon) transactionResult(ctx context.Context, row *sql.Rows) (*ffty var transaction fftypes.Transaction err := row.Scan( &transaction.ID, - &transaction.Subject.Type, - &transaction.Subject.Namespace, - &transaction.Subject.Reference, - &transaction.Subject.Signer, - &transaction.Hash, + &transaction.Type, + &transaction.Namespace, &transaction.Created, - &transaction.ProtocolID, &transaction.Status, - &transaction.Info, ) if err != nil { return nil, i18n.WrapError(ctx, err, i18n.MsgDBReadErr, "transactions") diff --git a/internal/database/sqlcommon/transaction_sql_test.go b/internal/database/sqlcommon/transaction_sql_test.go index ded010554b..dd6babf831 100644 --- a/internal/database/sqlcommon/transaction_sql_test.go +++ b/internal/database/sqlcommon/transaction_sql_test.go @@ -38,22 +38,16 @@ func TestTransactionE2EWithDB(t *testing.T) { // Create a new transaction entry transactionID := fftypes.NewUUID() transaction := &fftypes.Transaction{ - ID: transactionID, - Hash: fftypes.NewRandB32(), - Subject: fftypes.TransactionSubject{ - Type: fftypes.TransactionTypeBatchPin, - Namespace: "ns1", - Signer: "0x12345", - Reference: fftypes.NewUUID(), - }, - Created: fftypes.Now(), - Status: fftypes.OpStatusPending, + ID: transactionID, + Type: fftypes.TransactionTypeBatchPin, + Namespace: "ns1", + Status: fftypes.OpStatusPending, } s.callbacks.On("UUIDCollectionNSEvent", database.CollectionTransactions, fftypes.ChangeEventTypeCreated, "ns1", transactionID, mock.Anything).Return() s.callbacks.On("UUIDCollectionNSEvent", database.CollectionTransactions, fftypes.ChangeEventTypeUpdated, "ns1", transactionID, mock.Anything).Return() - err := s.UpsertTransaction(ctx, transaction, false) + err := s.UpsertTransaction(ctx, transaction) assert.NoError(t, err) // Check we get the exact same transaction back @@ -64,30 +58,15 @@ func TestTransactionE2EWithDB(t *testing.T) { transactionReadJson, _ := json.Marshal(&transactionRead) assert.Equal(t, string(transactionJson), string(transactionReadJson)) - // Update the transaction (this is testing what's possible at the database layer, - // and does not account for the verification that happens at the higher level) + // Update the transaction transactionUpdated := &fftypes.Transaction{ - ID: transactionID, - Hash: fftypes.NewRandB32(), - Subject: fftypes.TransactionSubject{ - Type: fftypes.TransactionTypeBatchPin, - Namespace: "ns1", - Signer: "0x12345", - Reference: fftypes.NewUUID(), - }, - Created: fftypes.Now(), - ProtocolID: "0x33333", - Status: fftypes.OpStatusFailed, - Info: fftypes.JSONObject{ - "some": "data", - }, + ID: transactionID, + Type: fftypes.TransactionTypeBatchPin, + Namespace: "ns1", + Created: transaction.Created, + Status: fftypes.OpStatusFailed, } - - // Check reject hash update - err = s.UpsertTransaction(context.Background(), transactionUpdated, false) - assert.Equal(t, database.HashMismatch, err) - - err = s.UpsertTransaction(context.Background(), transactionUpdated, true) + err = s.UpsertTransaction(context.Background(), transactionUpdated) assert.NoError(t, err) // Check we get the exact same message back - note the removal of one of the transaction elements @@ -101,8 +80,6 @@ func TestTransactionE2EWithDB(t *testing.T) { fb := database.TransactionQueryFactory.NewFilter(ctx) filter := fb.And( fb.Eq("id", transactionUpdated.ID.String()), - fb.Eq("protocolid", transactionUpdated.ProtocolID), - fb.Eq("signer", transactionUpdated.Subject.Signer), fb.Gt("created", "0"), ) transactions, res, err := s.GetTransactions(ctx, filter.Count(true)) @@ -140,7 +117,7 @@ func TestTransactionE2EWithDB(t *testing.T) { func TestUpsertTransactionFailBegin(t *testing.T) { s, mock := newMockProvider().init() mock.ExpectBegin().WillReturnError(fmt.Errorf("pop")) - err := s.UpsertTransaction(context.Background(), &fftypes.Transaction{}, true) + err := s.UpsertTransaction(context.Background(), &fftypes.Transaction{}) assert.Regexp(t, "FF10114", err) assert.NoError(t, mock.ExpectationsWereMet()) } @@ -151,7 +128,7 @@ func TestUpsertTransactionFailSelect(t *testing.T) { mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop")) mock.ExpectRollback() transactionID := fftypes.NewUUID() - err := s.UpsertTransaction(context.Background(), &fftypes.Transaction{ID: transactionID}, true) + err := s.UpsertTransaction(context.Background(), &fftypes.Transaction{ID: transactionID}) assert.Regexp(t, "FF10115", err) assert.NoError(t, mock.ExpectationsWereMet()) } @@ -163,7 +140,7 @@ func TestUpsertTransactionFailInsert(t *testing.T) { mock.ExpectExec("INSERT .*").WillReturnError(fmt.Errorf("pop")) mock.ExpectRollback() transactionID := fftypes.NewUUID() - err := s.UpsertTransaction(context.Background(), &fftypes.Transaction{ID: transactionID}, true) + err := s.UpsertTransaction(context.Background(), &fftypes.Transaction{ID: transactionID}) assert.Regexp(t, "FF10116", err) assert.NoError(t, mock.ExpectationsWereMet()) } @@ -175,7 +152,7 @@ func TestUpsertTransactionFailUpdate(t *testing.T) { mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(transactionID.String())) mock.ExpectExec("UPDATE .*").WillReturnError(fmt.Errorf("pop")) mock.ExpectRollback() - err := s.UpsertTransaction(context.Background(), &fftypes.Transaction{ID: transactionID}, true) + err := s.UpsertTransaction(context.Background(), &fftypes.Transaction{ID: transactionID}) assert.Regexp(t, "FF10117", err) assert.NoError(t, mock.ExpectationsWereMet()) } @@ -187,7 +164,7 @@ func TestUpsertTransactionFailCommit(t *testing.T) { mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"})) mock.ExpectExec("INSERT .*").WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit().WillReturnError(fmt.Errorf("pop")) - err := s.UpsertTransaction(context.Background(), &fftypes.Transaction{ID: transactionID}, true) + err := s.UpsertTransaction(context.Background(), &fftypes.Transaction{ID: transactionID}) assert.Regexp(t, "FF10119", err) assert.NoError(t, mock.ExpectationsWereMet()) } diff --git a/internal/definitions/definition_handler.go b/internal/definitions/definition_handler.go index 0e872d1e2c..1f0d0430aa 100644 --- a/internal/definitions/definition_handler.go +++ b/internal/definitions/definition_handler.go @@ -26,7 +26,6 @@ import ( "github.com/hyperledger/firefly/internal/data" "github.com/hyperledger/firefly/internal/log" "github.com/hyperledger/firefly/internal/privatemessaging" - "github.com/hyperledger/firefly/internal/txcommon" "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/dataexchange" "github.com/hyperledger/firefly/pkg/fftypes" @@ -57,7 +56,6 @@ type definitionHandlers struct { messaging privatemessaging.Manager assets assets.Manager contracts contracts.Manager - txhelper txcommon.Helper } func NewDefinitionHandlers(di database.Plugin, dx dataexchange.Plugin, dm data.Manager, bm broadcast.Manager, pm privatemessaging.Manager, am assets.Manager, cm contracts.Manager) DefinitionHandlers { @@ -69,7 +67,6 @@ func NewDefinitionHandlers(di database.Plugin, dx dataexchange.Plugin, dm data.M messaging: pm, assets: am, contracts: cm, - txhelper: txcommon.NewTransactionHelper(di), } } diff --git a/internal/definitions/definition_handler_tokenpool.go b/internal/definitions/definition_handler_tokenpool.go index 42ae5d6992..a544b21f10 100644 --- a/internal/definitions/definition_handler_tokenpool.go +++ b/internal/definitions/definition_handler_tokenpool.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -101,7 +101,7 @@ func (dh *definitionHandlers) handleTokenPoolBroadcast(ctx context.Context, msg return ActionReject, dh.rejectPool(ctx, pool) } - if err := dh.assets.ActivateTokenPool(ctx, pool, announce.TX); err != nil { + if err := dh.assets.ActivateTokenPool(ctx, pool, announce.Event); err != nil { log.L(ctx).Errorf("Failed to activate token pool '%s': %s", pool.ID, err) return ActionRetry, err } diff --git a/internal/definitions/definition_handler_tokenpool_test.go b/internal/definitions/definition_handler_tokenpool_test.go index d0203c7f42..c98c35d3a5 100644 --- a/internal/definitions/definition_handler_tokenpool_test.go +++ b/internal/definitions/definition_handler_tokenpool_test.go @@ -44,8 +44,8 @@ func newPoolAnnouncement() *fftypes.TokenPoolAnnouncement { }, } return &fftypes.TokenPoolAnnouncement{ - Pool: pool, - TX: &fftypes.Transaction{}, + Pool: pool, + Event: &fftypes.BlockchainEvent{}, } } @@ -84,11 +84,7 @@ func TestHandleSystemBroadcastTokenPoolActivateOK(t *testing.T) { mdi.On("UpsertTokenPool", context.Background(), mock.MatchedBy(func(p *fftypes.TokenPool) bool { return *p.ID == *pool.ID && p.Message == msg.Header.ID })).Return(nil) - mam.On("ActivateTokenPool", context.Background(), mock.MatchedBy(func(p *fftypes.TokenPool) bool { - return true - }), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return true - })).Return(nil) + mam.On("ActivateTokenPool", context.Background(), mock.AnythingOfType("*fftypes.TokenPool"), mock.AnythingOfType("*fftypes.BlockchainEvent")).Return(nil) action, err := sh.HandleSystemBroadcast(context.Background(), msg, data) assert.Equal(t, ActionWait, action) @@ -153,11 +149,7 @@ func TestHandleSystemBroadcastTokenPoolExisting(t *testing.T) { mdi.On("UpsertTokenPool", context.Background(), mock.MatchedBy(func(p *fftypes.TokenPool) bool { return *p.ID == *pool.ID && p.Message == msg.Header.ID })).Return(nil) - mam.On("ActivateTokenPool", context.Background(), mock.MatchedBy(func(p *fftypes.TokenPool) bool { - return true - }), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return true - })).Return(nil) + mam.On("ActivateTokenPool", context.Background(), mock.AnythingOfType("*fftypes.TokenPool"), mock.AnythingOfType("*fftypes.BlockchainEvent")).Return(nil) action, err := sh.HandleSystemBroadcast(context.Background(), msg, data) assert.Equal(t, ActionWait, action) @@ -277,11 +269,7 @@ func TestHandleSystemBroadcastTokenPoolActivateFail(t *testing.T) { mdi.On("UpsertTokenPool", context.Background(), mock.MatchedBy(func(p *fftypes.TokenPool) bool { return *p.ID == *pool.ID && p.Message == msg.Header.ID })).Return(nil) - mam.On("ActivateTokenPool", context.Background(), mock.MatchedBy(func(p *fftypes.TokenPool) bool { - return true - }), mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return true - })).Return(fmt.Errorf("pop")) + mam.On("ActivateTokenPool", context.Background(), mock.AnythingOfType("*fftypes.TokenPool"), mock.AnythingOfType("*fftypes.BlockchainEvent")).Return(fmt.Errorf("pop")) action, err := sh.HandleSystemBroadcast(context.Background(), msg, data) assert.Equal(t, ActionRetry, action) @@ -294,8 +282,8 @@ func TestHandleSystemBroadcastTokenPoolValidateFail(t *testing.T) { sh := newTestDefinitionHandlers(t) announce := &fftypes.TokenPoolAnnouncement{ - Pool: &fftypes.TokenPool{}, - TX: &fftypes.Transaction{}, + Pool: &fftypes.TokenPool{}, + Event: &fftypes.BlockchainEvent{}, } msg, data, err := buildPoolDefinitionMessage(announce) assert.NoError(t, err) diff --git a/internal/events/batch_pin_complete.go b/internal/events/batch_pin_complete.go index 2b99ec06cb..5adc358963 100644 --- a/internal/events/batch_pin_complete.go +++ b/internal/events/batch_pin_complete.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -32,29 +32,37 @@ import ( // // We must block here long enough to get the payload from the publicstorage, persist the messages in the correct // sequence, and also persist all the data. -func (em *eventManager) BatchPinComplete(bi blockchain.Plugin, batchPin *blockchain.BatchPin, signingIdentity string, protocolTxID string, additionalInfo fftypes.JSONObject) error { +func (em *eventManager) BatchPinComplete(bi blockchain.Plugin, batchPin *blockchain.BatchPin, signingIdentity string) error { + if batchPin.TransactionID == nil { + log.L(em.ctx).Errorf("Invalid BatchPin transaction - ID is nil") + return nil // move on + } + if err := fftypes.ValidateFFNameField(em.ctx, batchPin.Namespace, "namespace"); err != nil { + log.L(em.ctx).Errorf("Invalid transaction ID='%s' - invalid namespace '%s': %a", batchPin.TransactionID, batchPin.Namespace, err) + return nil // move on + } - log.L(em.ctx).Infof("-> BatchPinComplete batch=%s txn=%s signingIdentity=%s", batchPin.BatchID, protocolTxID, signingIdentity) + log.L(em.ctx).Infof("-> BatchPinComplete batch=%s txn=%s signingIdentity=%s", batchPin.BatchID, batchPin.Event.ProtocolID, signingIdentity) defer func() { - log.L(em.ctx).Infof("<- BatchPinComplete batch=%s txn=%s signingIdentity=%s", batchPin.BatchID, protocolTxID, signingIdentity) + log.L(em.ctx).Infof("<- BatchPinComplete batch=%s txn=%s signingIdentity=%s", batchPin.BatchID, batchPin.Event.ProtocolID, signingIdentity) }() - log.L(em.ctx).Tracef("BatchPinComplete batch=%s info: %+v", batchPin.BatchID, additionalInfo) + log.L(em.ctx).Tracef("BatchPinComplete batch=%s info: %+v", batchPin.BatchID, batchPin.Event.Info) if batchPin.BatchPayloadRef != "" { - return em.handleBroadcastPinComplete(batchPin, signingIdentity, protocolTxID, additionalInfo) + return em.handleBroadcastPinComplete(batchPin, signingIdentity) } - return em.handlePrivatePinComplete(batchPin, signingIdentity, protocolTxID, additionalInfo) + return em.handlePrivatePinComplete(batchPin) } -func (em *eventManager) handlePrivatePinComplete(batchPin *blockchain.BatchPin, signingIdentity string, protocolTxID string, additionalInfo fftypes.JSONObject) error { +func (em *eventManager) handlePrivatePinComplete(batchPin *blockchain.BatchPin) error { // Here we simple record all the pins as parked, and emit an event for the aggregator // to check whether the messages in the batch have been written. return em.retry.Do(em.ctx, "persist private batch pins", func(attempt int) (bool, error) { // We process the batch into the DB as a single transaction (if transactions are supported), both for // efficiency and to minimize the chance of duplicates (although at-least-once delivery is the core model) err := em.database.RunAsGroup(em.ctx, func(ctx context.Context) error { - valid, err := em.persistBatchTransaction(ctx, batchPin, signingIdentity, protocolTxID, additionalInfo) - if valid && err == nil { + err := em.persistBatchTransaction(ctx, batchPin) + if err == nil { err = em.persistContexts(ctx, batchPin, true) } return err @@ -63,17 +71,12 @@ func (em *eventManager) handlePrivatePinComplete(batchPin *blockchain.BatchPin, }) } -func (em *eventManager) persistBatchTransaction(ctx context.Context, batchPin *blockchain.BatchPin, signingIdentity string, protocolTxID string, additionalInfo fftypes.JSONObject) (valid bool, err error) { - return em.txhelper.PersistTransaction(ctx, &fftypes.Transaction{ - ID: batchPin.TransactionID, - Subject: fftypes.TransactionSubject{ - Namespace: batchPin.Namespace, - Type: fftypes.TransactionTypeBatchPin, - Signer: signingIdentity, - Reference: batchPin.BatchID, - }, - ProtocolID: protocolTxID, - Info: additionalInfo, +func (em *eventManager) persistBatchTransaction(ctx context.Context, batchPin *blockchain.BatchPin) error { + return em.database.UpsertTransaction(ctx, &fftypes.Transaction{ + ID: batchPin.TransactionID, + Namespace: batchPin.Namespace, + Type: fftypes.TransactionTypeBatchPin, + Status: fftypes.OpStatusSucceeded, }) } @@ -92,7 +95,7 @@ func (em *eventManager) persistContexts(ctx context.Context, batchPin *blockchai return nil } -func (em *eventManager) handleBroadcastPinComplete(batchPin *blockchain.BatchPin, signingIdentity string, protocolTxID string, additionalInfo fftypes.JSONObject) error { +func (em *eventManager) handleBroadcastPinComplete(batchPin *blockchain.BatchPin, signingIdentity string) error { var body io.ReadCloser if err := em.retry.Do(em.ctx, "retrieve data", func(attempt int) (retry bool, err error) { body, err = em.publicstorage.RetrieveData(em.ctx, batchPin.BatchPayloadRef) @@ -105,7 +108,7 @@ func (em *eventManager) handleBroadcastPinComplete(batchPin *blockchain.BatchPin var batch *fftypes.Batch err := json.NewDecoder(body).Decode(&batch) if err != nil { - log.L(em.ctx).Errorf("Failed to parse payload referred in batch ID '%s' from transaction '%s'", batchPin.BatchID, protocolTxID) + log.L(em.ctx).Errorf("Failed to parse payload referred in batch ID '%s' from transaction '%s'", batchPin.BatchID, batchPin.Event.ProtocolID) return nil // log and swallow unprocessable data } body.Close() @@ -118,14 +121,19 @@ func (em *eventManager) handleBroadcastPinComplete(batchPin *blockchain.BatchPin // We process the batch into the DB as a single transaction (if transactions are supported), both for // efficiency and to minimize the chance of duplicates (although at-least-once delivery is the core model) err := em.database.RunAsGroup(em.ctx, func(ctx context.Context) error { - valid, err := em.persistBatchTransaction(ctx, batchPin, signingIdentity, protocolTxID, additionalInfo) + chainEvent := buildBlockchainEvent(batchPin.Namespace, nil, &batchPin.Event, &batch.Payload.TX) + if err := em.persistBlockchainEvent(ctx, chainEvent); err != nil { + return err + } + if err := em.persistBatchTransaction(ctx, batchPin); err != nil { + return err + } + // Note that in the case of a bad batch broadcast, we don't store the pin. Because we know we // are never going to be able to process it (we retrieved it successfully, it's just invalid). + valid, err := em.persistBatchFromBroadcast(ctx, batch, batchPin.BatchHash, signingIdentity) if valid && err == nil { - valid, err = em.persistBatchFromBroadcast(ctx, batch, batchPin.BatchHash, signingIdentity) - if valid && err == nil { - err = em.persistContexts(ctx, batchPin, false) - } + err = em.persistContexts(ctx, batchPin, false) } return err }) diff --git a/internal/events/batch_pin_complete_test.go b/internal/events/batch_pin_complete_test.go index 3286aa5983..ef3f526d16 100644 --- a/internal/events/batch_pin_complete_test.go +++ b/internal/events/batch_pin_complete_test.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -45,6 +45,10 @@ func TestBatchPinCompleteOkBroadcast(t *testing.T) { BatchID: fftypes.NewUUID(), BatchPayloadRef: "Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", Contexts: []*fftypes.Bytes32{fftypes.NewRandB32()}, + Event: blockchain.Event{ + Name: "BatchPin", + ProtocolID: "tx1", + }, } batchData := &fftypes.Batch{ ID: batch.BatchID, @@ -83,16 +87,26 @@ func TestBatchPinCompleteOkBroadcast(t *testing.T) { a[1].(func(ctx context.Context) error)(a[0].(context.Context)), } } - mdi.On("GetTransactionByID", mock.Anything, batchData.Payload.TX.ID).Return(nil, nil) - mdi.On("UpsertTransaction", mock.Anything, mock.Anything, false).Return(nil) - mdi.On("UpsertPin", mock.Anything, mock.Anything).Return(nil) - mdi.On("UpsertBatch", mock.Anything, mock.Anything, false).Return(nil) + + mdi.On("InsertBlockchainEvent", mock.Anything, mock.MatchedBy(func(e *fftypes.BlockchainEvent) bool { + return e.Name == batch.Event.Name + })).Return(fmt.Errorf("pop")).Once() + mdi.On("InsertBlockchainEvent", mock.Anything, mock.MatchedBy(func(e *fftypes.BlockchainEvent) bool { + return e.Name == batch.Event.Name + })).Return(nil).Times(2) + mdi.On("InsertEvent", mock.Anything, mock.MatchedBy(func(e *fftypes.Event) bool { + return e.Type == fftypes.EventTypeBlockchainEvent + })).Return(nil).Times(2) + mdi.On("UpsertTransaction", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")).Once() + mdi.On("UpsertTransaction", mock.Anything, mock.Anything).Return(nil).Once() + mdi.On("UpsertPin", mock.Anything, mock.Anything).Return(nil).Once() + mdi.On("UpsertBatch", mock.Anything, mock.Anything, false).Return(nil).Once() mbi := &blockchainmocks.Plugin{} mim := em.identity.(*identitymanagermocks.Manager) mim.On("ResolveSigningKeyIdentity", mock.Anything, "0x12345").Return("author1", nil) - err = em.BatchPinComplete(mbi, batch, "0x12345", "tx1", nil) + err = em.BatchPinComplete(mbi, batch, "0x12345") assert.NoError(t, err) mdi.AssertExpectations(t) @@ -132,12 +146,11 @@ func TestBatchPinCompleteOkPrivate(t *testing.T) { mdi := em.database.(*databasemocks.Plugin) mdi.On("RunAsGroup", mock.Anything, mock.Anything).Return(nil) - mdi.On("GetTransactionByID", mock.Anything, batchData.Payload.TX.ID).Return(nil, nil) - mdi.On("UpsertTransaction", mock.Anything, mock.Anything, false).Return(nil) + mdi.On("UpsertTransaction", mock.Anything, mock.Anything).Return(nil) mdi.On("UpsertPin", mock.Anything, mock.Anything).Return(nil) mbi := &blockchainmocks.Plugin{} - err = em.BatchPinComplete(mbi, batch, "0x12345", "tx1", nil) + err = em.BatchPinComplete(mbi, batch, "0x12345") assert.NoError(t, err) // Call through to persistBatch - the hash of our batch will be invalid, @@ -153,6 +166,7 @@ func TestSequencedBroadcastRetrieveIPFSFail(t *testing.T) { em, cancel := newTestEventManager(t) batch := &blockchain.BatchPin{ + Namespace: "ns", TransactionID: fftypes.NewUUID(), BatchID: fftypes.NewUUID(), BatchPayloadRef: "Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", @@ -164,7 +178,7 @@ func TestSequencedBroadcastRetrieveIPFSFail(t *testing.T) { mpi.On("RetrieveData", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("pop")) mbi := &blockchainmocks.Plugin{} - err := em.BatchPinComplete(mbi, batch, "0x12345", "tx1", nil) + err := em.BatchPinComplete(mbi, batch, "0x12345") mpi.AssertExpectations(t) assert.Regexp(t, "FF10158", err) } @@ -174,6 +188,7 @@ func TestBatchPinCompleteBadData(t *testing.T) { defer cancel() batch := &blockchain.BatchPin{ + Namespace: "ns", TransactionID: fftypes.NewUUID(), BatchID: fftypes.NewUUID(), BatchPayloadRef: "Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", @@ -185,10 +200,35 @@ func TestBatchPinCompleteBadData(t *testing.T) { mpi.On("RetrieveData", mock.Anything, mock.Anything).Return(batchReadCloser, nil) mbi := &blockchainmocks.Plugin{} - err := em.BatchPinComplete(mbi, batch, "0x12345", "tx1", nil) + err := em.BatchPinComplete(mbi, batch, "0x12345") assert.NoError(t, err) // We do not return a blocking error in the case of bad data stored in IPFS } +func TestBatchPinCompleteNoTX(t *testing.T) { + em, cancel := newTestEventManager(t) + defer cancel() + + batch := &blockchain.BatchPin{} + mbi := &blockchainmocks.Plugin{} + + err := em.BatchPinComplete(mbi, batch, "0x12345") + assert.NoError(t, err) +} + +func TestBatchPinCompleteBadNamespace(t *testing.T) { + em, cancel := newTestEventManager(t) + defer cancel() + + batch := &blockchain.BatchPin{ + Namespace: "!bad", + TransactionID: fftypes.NewUUID(), + } + mbi := &blockchainmocks.Plugin{} + + err := em.BatchPinComplete(mbi, batch, "0x12345") + assert.NoError(t, err) +} + func TestPersistBatchMissingID(t *testing.T) { em, cancel := newTestEventManager(t) defer cancel() diff --git a/internal/events/contract_event.go b/internal/events/contract_event.go index 01eb44e84e..d8e9a74240 100644 --- a/internal/events/contract_event.go +++ b/internal/events/contract_event.go @@ -24,32 +24,50 @@ import ( "github.com/hyperledger/firefly/pkg/fftypes" ) -func (em *eventManager) ContractEvent(blockchainEvent *blockchain.ContractEvent) error { +func buildBlockchainEvent(ns string, subID *fftypes.UUID, event *blockchain.Event, tx *fftypes.TransactionRef) *fftypes.BlockchainEvent { + ev := &fftypes.BlockchainEvent{ + ID: fftypes.NewUUID(), + Namespace: ns, + Subscription: subID, + Source: event.Source, + ProtocolID: event.ProtocolID, + Name: event.Name, + Output: event.Output, + Info: event.Info, + Timestamp: event.Timestamp, + } + if tx != nil { + ev.TX = *tx + } + return ev +} + +func (em *eventManager) persistBlockchainEvent(ctx context.Context, chainEvent *fftypes.BlockchainEvent) error { + if err := em.database.InsertBlockchainEvent(ctx, chainEvent); err != nil { + return err + } + ffEvent := fftypes.NewEvent(fftypes.EventTypeBlockchainEvent, chainEvent.Namespace, chainEvent.ID) + if err := em.database.InsertEvent(ctx, ffEvent); err != nil { + return err + } + return nil +} + +func (em *eventManager) ContractEvent(event *blockchain.ContractEvent) error { return em.retry.Do(em.ctx, "persist contract event", func(attempt int) (bool, error) { err := em.database.RunAsGroup(em.ctx, func(ctx context.Context) error { // TODO: should cache this lookup for efficiency - sub, err := em.database.GetContractSubscriptionByProtocolID(ctx, blockchainEvent.Subscription) + sub, err := em.database.GetContractSubscriptionByProtocolID(ctx, event.Subscription) if err != nil { return err } if sub == nil { - log.L(ctx).Warnf("Event received from unknown subscription %s", blockchainEvent.Subscription) + log.L(ctx).Warnf("Event received from unknown subscription %s", event.Subscription) return nil // no retry } - contractEvent := &fftypes.ContractEvent{ - ID: fftypes.NewUUID(), - Namespace: sub.Namespace, - Subscription: sub.ID, - Name: blockchainEvent.Name, - Outputs: blockchainEvent.Outputs, - Info: blockchainEvent.Info, - Timestamp: blockchainEvent.Timestamp, - } - if err = em.database.InsertContractEvent(ctx, contractEvent); err != nil { - return err - } - event := fftypes.NewEvent(fftypes.EventTypeContractEvent, contractEvent.Namespace, contractEvent.ID) - if err = em.database.InsertEvent(ctx, event); err != nil { + + chainEvent := buildBlockchainEvent(sub.Namespace, sub.ID, &event.Event, nil) + if err := em.persistBlockchainEvent(ctx, chainEvent); err != nil { return err } return nil diff --git a/internal/events/contract_event_test.go b/internal/events/contract_event_test.go index cb2b1c8bdd..4cd1672e11 100644 --- a/internal/events/contract_event_test.go +++ b/internal/events/contract_event_test.go @@ -33,12 +33,14 @@ func TestContractEventWithRetries(t *testing.T) { ev := &blockchain.ContractEvent{ Subscription: "sb-1", - Name: "Changed", - Outputs: fftypes.JSONObject{ - "value": "1", - }, - Info: fftypes.JSONObject{ - "blockNumber": "10", + Event: blockchain.Event{ + Name: "Changed", + Output: fftypes.JSONObject{ + "value": "1", + }, + Info: fftypes.JSONObject{ + "blockNumber": "10", + }, }, } sub := &fftypes.ContractSubscription{ @@ -50,14 +52,14 @@ func TestContractEventWithRetries(t *testing.T) { mdi := em.database.(*databasemocks.Plugin) mdi.On("GetContractSubscriptionByProtocolID", mock.Anything, "sb-1").Return(nil, fmt.Errorf("pop")).Once() mdi.On("GetContractSubscriptionByProtocolID", mock.Anything, "sb-1").Return(sub, nil).Times(3) - mdi.On("InsertContractEvent", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")).Once() - mdi.On("InsertContractEvent", mock.Anything, mock.MatchedBy(func(e *fftypes.ContractEvent) bool { + mdi.On("InsertBlockchainEvent", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")).Once() + mdi.On("InsertBlockchainEvent", mock.Anything, mock.MatchedBy(func(e *fftypes.BlockchainEvent) bool { eventID = e.ID return *e.Subscription == *sub.ID && e.Name == "Changed" && e.Namespace == "ns" })).Return(nil).Times(2) mdi.On("InsertEvent", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")).Once() mdi.On("InsertEvent", mock.Anything, mock.MatchedBy(func(e *fftypes.Event) bool { - return e.Type == fftypes.EventTypeContractEvent && e.Reference != nil && e.Reference == eventID + return e.Type == fftypes.EventTypeBlockchainEvent && e.Reference != nil && e.Reference == eventID })).Return(nil).Once() err := em.ContractEvent(ev) @@ -72,12 +74,14 @@ func TestContractEventUnknownSubscription(t *testing.T) { ev := &blockchain.ContractEvent{ Subscription: "sb-1", - Name: "Changed", - Outputs: fftypes.JSONObject{ - "value": "1", - }, - Info: fftypes.JSONObject{ - "blockNumber": "10", + Event: blockchain.Event{ + Name: "Changed", + Output: fftypes.JSONObject{ + "value": "1", + }, + Info: fftypes.JSONObject{ + "blockNumber": "10", + }, }, } diff --git a/internal/events/event_manager.go b/internal/events/event_manager.go index 5c183b2107..529e5ca8cd 100644 --- a/internal/events/event_manager.go +++ b/internal/events/event_manager.go @@ -35,7 +35,6 @@ import ( "github.com/hyperledger/firefly/internal/privatemessaging" "github.com/hyperledger/firefly/internal/retry" "github.com/hyperledger/firefly/internal/sysmessaging" - "github.com/hyperledger/firefly/internal/txcommon" "github.com/hyperledger/firefly/pkg/blockchain" "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/dataexchange" @@ -58,7 +57,8 @@ type EventManager interface { // Bound blockchain callbacks OperationUpdate(plugin fftypes.Named, operationID *fftypes.UUID, txState blockchain.TransactionStatus, errorMessage string, opOutput fftypes.JSONObject) error - BatchPinComplete(bi blockchain.Plugin, batch *blockchain.BatchPin, author string, protocolTxID string, additionalInfo fftypes.JSONObject) error + BatchPinComplete(bi blockchain.Plugin, batch *blockchain.BatchPin, signingIdentity string) error + ContractEvent(event *blockchain.ContractEvent) error // Bound dataexchange callbacks TransferResult(dx dataexchange.Plugin, trackingID string, status fftypes.OpStatus, update fftypes.TransportStatusUpdate) error @@ -66,11 +66,8 @@ type EventManager interface { MessageReceived(dx dataexchange.Plugin, peerID string, data []byte) (manifest string, err error) // Bound token callbacks - TokenPoolCreated(ti tokens.Plugin, pool *tokens.TokenPool, protocolTxID string, additionalInfo fftypes.JSONObject) error - TokensTransferred(ti tokens.Plugin, poolProtocolID string, transfer *fftypes.TokenTransfer, protocolTxID string, additionalInfo fftypes.JSONObject) error - - // Bound contract callbacks - ContractEvent(event *blockchain.ContractEvent) error + TokenPoolCreated(ti tokens.Plugin, pool *tokens.TokenPool) error + TokensTransferred(ti tokens.Plugin, transfer *tokens.TokenTransfer) error // Internal events sysmessaging.SystemEvents @@ -86,7 +83,6 @@ type eventManager struct { data data.Manager subManager *subscriptionManager retry retry.Retry - txhelper txcommon.Helper aggregator *aggregator broadcast broadcast.Manager messaging privatemessaging.Manager @@ -120,7 +116,6 @@ func NewEventManager(ctx context.Context, ni sysmessaging.LocalNodeInfo, pi publ MaximumDelay: config.GetDuration(config.EventAggregatorRetryMaxDelay), Factor: config.GetFloat64(config.EventAggregatorRetryFactor), }, - txhelper: txcommon.NewTransactionHelper(di), defaultTransport: config.GetString(config.EventTransportsDefault), opCorrelationRetries: config.GetInt(config.EventAggregatorOpCorrelationRetries), newEventNotifier: newEventNotifier, diff --git a/internal/events/token_pool_created.go b/internal/events/token_pool_created.go index 8808ecf3d8..61ad3cc67b 100644 --- a/internal/events/token_pool_created.go +++ b/internal/events/token_pool_created.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -21,6 +21,7 @@ import ( "github.com/hyperledger/firefly/internal/log" "github.com/hyperledger/firefly/internal/txcommon" + "github.com/hyperledger/firefly/pkg/blockchain" "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/hyperledger/firefly/pkg/tokens" @@ -40,24 +41,22 @@ func addPoolDetailsFromPlugin(ffPool *fftypes.TokenPool, pluginPool *tokens.Toke } } -func poolTransaction(pool *fftypes.TokenPool, status fftypes.OpStatus, protocolTxID string, additionalInfo fftypes.JSONObject) *fftypes.Transaction { +func poolTransaction(pool *fftypes.TokenPool, status fftypes.OpStatus) *fftypes.Transaction { return &fftypes.Transaction{ - ID: pool.TX.ID, - Status: status, - Subject: fftypes.TransactionSubject{ - Namespace: pool.Namespace, - Type: pool.TX.Type, - Signer: pool.Key, - Reference: pool.ID, - }, - ProtocolID: protocolTxID, - Info: additionalInfo, + ID: pool.TX.ID, + Status: status, + Namespace: pool.Namespace, + Type: pool.TX.Type, } } -func (em *eventManager) confirmPool(ctx context.Context, pool *fftypes.TokenPool, protocolTxID string, additionalInfo fftypes.JSONObject) error { - tx := poolTransaction(pool, fftypes.OpStatusSucceeded, protocolTxID, additionalInfo) - if valid, err := em.txhelper.PersistTransaction(ctx, tx); !valid || err != nil { +func (em *eventManager) confirmPool(ctx context.Context, pool *fftypes.TokenPool, ev *blockchain.Event) error { + chainEvent := buildBlockchainEvent(pool.Namespace, nil, ev, &pool.TX) + if err := em.persistBlockchainEvent(ctx, chainEvent); err != nil { + return err + } + tx := poolTransaction(pool, fftypes.OpStatusSucceeded) + if err := em.database.UpsertTransaction(ctx, tx); err != nil { return err } pool.State = fftypes.TokenPoolStateConfirmed @@ -94,11 +93,8 @@ func (em *eventManager) shouldConfirm(ctx context.Context, pool *tokens.TokenPoo // Unknown pool state - should only happen on first run after database migration // Activate the pool, then immediately confirm // TODO: can this state eventually be removed? - tx, err := em.database.GetTransactionByID(ctx, existingPool.TX.ID) - if err != nil { - return nil, err - } - if err = em.assets.ActivateTokenPool(ctx, existingPool, tx); err != nil { + ev := buildBlockchainEvent(existingPool.Namespace, nil, &pool.Event, &existingPool.TX) + if err = em.assets.ActivateTokenPool(ctx, existingPool, ev); err != nil { log.L(ctx).Errorf("Failed to activate token pool '%s': %s", existingPool.ID, err) return nil, err } @@ -107,10 +103,6 @@ func (em *eventManager) shouldConfirm(ctx context.Context, pool *tokens.TokenPoo } func (em *eventManager) shouldAnnounce(ctx context.Context, ti tokens.Plugin, pool *tokens.TokenPool) (announcePool *fftypes.TokenPool, err error) { - if pool.TransactionID == nil { - return nil, nil - } - op, err := em.findTokenPoolCreateOp(ctx, pool.TransactionID) if err != nil { return nil, err @@ -139,7 +131,12 @@ func (em *eventManager) shouldAnnounce(ctx context.Context, ti tokens.Plugin, po // It will be at least invoked on the submitter when the pool is first created, to trigger the submitter to announce it. // It will be invoked on every node (including the submitter) after the pool is announced+activated, to trigger confirmation of the pool. // When received in any other scenario, it should be ignored. -func (em *eventManager) TokenPoolCreated(ti tokens.Plugin, pool *tokens.TokenPool, protocolTxID string, additionalInfo fftypes.JSONObject) (err error) { +func (em *eventManager) TokenPoolCreated(ti tokens.Plugin, pool *tokens.TokenPool) (err error) { + if pool.TransactionID == nil { + log.L(em.ctx).Errorf("Invalid token pool transaction - ID is nil") + return nil // move on + } + var batchID *fftypes.UUID var announcePool *fftypes.TokenPool @@ -157,7 +154,7 @@ func (em *eventManager) TokenPoolCreated(ti tokens.Plugin, pool *tokens.TokenPoo } else if msg != nil { batchID = msg.BatchID // trigger rewind after completion of database transaction } - return em.confirmPool(ctx, existingPool, protocolTxID, additionalInfo) + return em.confirmPool(ctx, existingPool, &pool.Event) } // See if this pool was submitted locally and needs to be announced @@ -168,7 +165,7 @@ func (em *eventManager) TokenPoolCreated(ti tokens.Plugin, pool *tokens.TokenPoo } // Otherwise this event can be ignored - log.L(ctx).Debugf("Ignoring token pool transaction '%s' - pool %s is not active", protocolTxID, pool.ProtocolID) + log.L(ctx).Debugf("Ignoring token pool transaction '%s' - pool %s is not active", pool.Event.ProtocolID, pool.ProtocolID) return nil }) return err != nil, err @@ -181,12 +178,12 @@ func (em *eventManager) TokenPoolCreated(ti tokens.Plugin, pool *tokens.TokenPoo em.aggregator.offchainBatches <- batchID } - // Announce the details of the new token pool and the transaction object + // Announce the details of the new token pool with the blockchain event details // Other nodes will pass these details to their own token connector for validation/activation of the pool if announcePool != nil { broadcast := &fftypes.TokenPoolAnnouncement{ - Pool: announcePool, - TX: poolTransaction(announcePool, fftypes.OpStatusPending, protocolTxID, additionalInfo), + Pool: announcePool, + Event: buildBlockchainEvent(announcePool.Namespace, nil, &pool.Event, &announcePool.TX), } log.L(em.ctx).Infof("Announcing token pool id=%s author=%s", announcePool.ID, pool.Key) _, err = em.broadcast.BroadcastTokenPool(em.ctx, announcePool.Namespace, broadcast, false) diff --git a/internal/events/token_pool_created_test.go b/internal/events/token_pool_created_test.go index c5079d2951..952141df9b 100644 --- a/internal/events/token_pool_created_test.go +++ b/internal/events/token_pool_created_test.go @@ -24,6 +24,7 @@ import ( "github.com/hyperledger/firefly/mocks/broadcastmocks" "github.com/hyperledger/firefly/mocks/databasemocks" "github.com/hyperledger/firefly/mocks/tokenmocks" + "github.com/hyperledger/firefly/pkg/blockchain" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/hyperledger/firefly/pkg/tokens" "github.com/stretchr/testify/assert" @@ -38,19 +39,23 @@ func TestTokenPoolCreatedIgnore(t *testing.T) { txID := fftypes.NewUUID() operations := []*fftypes.Operation{} + info := fftypes.JSONObject{"some": "info"} pool := &tokens.TokenPool{ Type: fftypes.TokenTypeFungible, ProtocolID: "123", Key: "0x0", TransactionID: txID, Connector: "erc1155", + Event: blockchain.Event{ + ProtocolID: "tx1", + Info: info, + }, } mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "123").Return(nil, nil, nil) mdi.On("GetOperations", em.ctx, mock.Anything).Return(operations, nil, nil) - info := fftypes.JSONObject{"some": "info"} - err := em.TokenPoolCreated(mti, pool, "tx1", info) + err := em.TokenPoolCreated(mti, pool) assert.NoError(t, err) mdi.AssertExpectations(t) @@ -59,24 +64,23 @@ func TestTokenPoolCreatedIgnore(t *testing.T) { func TestTokenPoolCreatedIgnoreNoTX(t *testing.T) { em, cancel := newTestEventManager(t) defer cancel() - mdi := em.database.(*databasemocks.Plugin) mti := &tokenmocks.Plugin{} + info := fftypes.JSONObject{"some": "info"} pool := &tokens.TokenPool{ Type: fftypes.TokenTypeFungible, ProtocolID: "123", Key: "0x0", TransactionID: nil, Connector: "erc1155", + Event: blockchain.Event{ + ProtocolID: "tx1", + Info: info, + }, } - mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "123").Return(nil, nil, nil) - - info := fftypes.JSONObject{"some": "info"} - err := em.TokenPoolCreated(mti, pool, "tx1", info) + err := em.TokenPoolCreated(mti, pool) assert.NoError(t, err) - - mdi.AssertExpectations(t) } func TestTokenPoolCreatedConfirm(t *testing.T) { @@ -86,11 +90,18 @@ func TestTokenPoolCreatedConfirm(t *testing.T) { mti := &tokenmocks.Plugin{} txID := fftypes.NewUUID() + info := fftypes.JSONObject{"some": "info"} chainPool := &tokens.TokenPool{ - Type: fftypes.TokenTypeFungible, - ProtocolID: "123", - Key: "0x0", - Connector: "erc1155", + Type: fftypes.TokenTypeFungible, + ProtocolID: "123", + Key: "0x0", + Connector: "erc1155", + TransactionID: txID, + Event: blockchain.Event{ + Name: "TokenPool", + ProtocolID: "tx1", + Info: info, + }, } storedPool := &fftypes.TokenPool{ Namespace: "ns1", @@ -103,33 +114,29 @@ func TestTokenPoolCreatedConfirm(t *testing.T) { ID: txID, }, } - storedTX := &fftypes.Transaction{ - Subject: fftypes.TransactionSubject{ - Namespace: "ns1", - Reference: storedPool.ID, - Signer: storedPool.Key, - Type: fftypes.TransactionTypeTokenPool, - }, - } storedMessage := &fftypes.Message{ BatchID: fftypes.NewUUID(), } mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "123").Return(nil, fmt.Errorf("pop")).Once() mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "123").Return(storedPool, nil).Times(2) - mdi.On("GetTransactionByID", em.ctx, txID).Return(storedTX, nil) + mdi.On("InsertBlockchainEvent", em.ctx, mock.MatchedBy(func(e *fftypes.BlockchainEvent) bool { + return e.Name == chainPool.Event.Name + })).Return(nil).Once() + mdi.On("InsertEvent", em.ctx, mock.MatchedBy(func(e *fftypes.Event) bool { + return e.Type == fftypes.EventTypeBlockchainEvent + })).Return(nil).Once() mdi.On("UpsertTransaction", em.ctx, mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return *tx.Subject.Reference == *storedTX.Subject.Reference - }), false).Return(nil) - mdi.On("UpsertTokenPool", em.ctx, storedPool).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenPool + })).Return(nil).Once() + mdi.On("UpsertTokenPool", em.ctx, storedPool).Return(nil).Once() mdi.On("InsertEvent", em.ctx, mock.MatchedBy(func(e *fftypes.Event) bool { return e.Type == fftypes.EventTypePoolConfirmed && *e.Reference == *storedPool.ID - })).Return(nil) + })).Return(nil).Once() mdi.On("GetMessageByID", em.ctx, storedPool.Message).Return(nil, fmt.Errorf("pop")).Once() mdi.On("GetMessageByID", em.ctx, storedPool.Message).Return(storedMessage, nil).Once() - info := fftypes.JSONObject{"some": "info"} - err := em.TokenPoolCreated(mti, chainPool, "tx1", info) + err := em.TokenPoolCreated(mti, chainPool) assert.NoError(t, err) mdi.AssertExpectations(t) @@ -142,11 +149,17 @@ func TestTokenPoolCreatedAlreadyConfirmed(t *testing.T) { mti := &tokenmocks.Plugin{} txID := fftypes.NewUUID() + info := fftypes.JSONObject{"some": "info"} chainPool := &tokens.TokenPool{ - Type: fftypes.TokenTypeFungible, - ProtocolID: "123", - Key: "0x0", - Connector: "erc1155", + Type: fftypes.TokenTypeFungible, + ProtocolID: "123", + Key: "0x0", + Connector: "erc1155", + TransactionID: txID, + Event: blockchain.Event{ + ProtocolID: "tx1", + Info: info, + }, } storedPool := &fftypes.TokenPool{ Namespace: "ns1", @@ -161,8 +174,7 @@ func TestTokenPoolCreatedAlreadyConfirmed(t *testing.T) { mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "123").Return(storedPool, nil) - info := fftypes.JSONObject{"some": "info"} - err := em.TokenPoolCreated(mti, chainPool, "tx1", info) + err := em.TokenPoolCreated(mti, chainPool) assert.NoError(t, err) mdi.AssertExpectations(t) @@ -176,11 +188,17 @@ func TestTokenPoolCreatedMigrate(t *testing.T) { mti := &tokenmocks.Plugin{} txID := fftypes.NewUUID() + info := fftypes.JSONObject{"some": "info"} chainPool := &tokens.TokenPool{ - Type: fftypes.TokenTypeFungible, - ProtocolID: "123", - Key: "0x0", - Connector: "magic-tokens", + Type: fftypes.TokenTypeFungible, + ProtocolID: "123", + Key: "0x0", + Connector: "magic-tokens", + TransactionID: txID, + Event: blockchain.Event{ + ProtocolID: "tx1", + Info: info, + }, } storedPool := &fftypes.TokenPool{ Namespace: "ns1", @@ -192,40 +210,69 @@ func TestTokenPoolCreatedMigrate(t *testing.T) { ID: txID, }, } - storedTX := &fftypes.Transaction{ - Subject: fftypes.TransactionSubject{ - Namespace: "ns1", - Reference: storedPool.ID, - Signer: storedPool.Key, - Type: fftypes.TransactionTypeTokenPool, - }, - } storedMessage := &fftypes.Message{ BatchID: fftypes.NewUUID(), } - mdi.On("GetTokenPoolByProtocolID", em.ctx, "magic-tokens", "123").Return(storedPool, nil).Times(3) - mdi.On("GetTransactionByID", em.ctx, storedPool.TX.ID).Return(nil, fmt.Errorf("pop")).Once() - mdi.On("GetTransactionByID", em.ctx, storedPool.TX.ID).Return(storedTX, nil).Times(3) + mdi.On("GetTokenPoolByProtocolID", em.ctx, "magic-tokens", "123").Return(storedPool, nil).Times(2) + mdi.On("InsertBlockchainEvent", em.ctx, mock.MatchedBy(func(e *fftypes.BlockchainEvent) bool { + return e.Name == chainPool.Event.Name + })).Return(nil).Once() + mdi.On("InsertEvent", em.ctx, mock.MatchedBy(func(e *fftypes.Event) bool { + return e.Type == fftypes.EventTypeBlockchainEvent + })).Return(nil).Once() mdi.On("UpsertTransaction", em.ctx, mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return *tx.Subject.Reference == *storedTX.Subject.Reference - }), false).Return(nil).Once() + return tx.Type == fftypes.TransactionTypeTokenPool + })).Return(nil).Once() mdi.On("UpsertTokenPool", em.ctx, storedPool).Return(nil).Once() mdi.On("InsertEvent", em.ctx, mock.MatchedBy(func(e *fftypes.Event) bool { return e.Type == fftypes.EventTypePoolConfirmed && *e.Reference == *storedPool.ID })).Return(nil).Once() - mam.On("ActivateTokenPool", em.ctx, storedPool, storedTX).Return(fmt.Errorf("pop")).Once() - mam.On("ActivateTokenPool", em.ctx, storedPool, storedTX).Return(nil).Once() + mam.On("ActivateTokenPool", em.ctx, storedPool, mock.MatchedBy(func(e *fftypes.BlockchainEvent) bool { + return e.ProtocolID == chainPool.Event.ProtocolID + })).Return(fmt.Errorf("pop")).Once() + mam.On("ActivateTokenPool", em.ctx, storedPool, mock.MatchedBy(func(e *fftypes.BlockchainEvent) bool { + return e.ProtocolID == chainPool.Event.ProtocolID + })).Return(nil).Once() mdi.On("GetMessageByID", em.ctx, storedPool.Message).Return(storedMessage, nil) - info := fftypes.JSONObject{"some": "info"} - err := em.TokenPoolCreated(mti, chainPool, "tx1", info) + err := em.TokenPoolCreated(mti, chainPool) assert.NoError(t, err) mdi.AssertExpectations(t) mam.AssertExpectations(t) } +func TestConfirmPoolBlockchainEventFail(t *testing.T) { + em, cancel := newTestEventManager(t) + defer cancel() + mdi := em.database.(*databasemocks.Plugin) + + txID := fftypes.NewUUID() + storedPool := &fftypes.TokenPool{ + Namespace: "ns1", + ID: fftypes.NewUUID(), + Key: "0x0", + State: fftypes.TokenPoolStatePending, + TX: fftypes.TransactionRef{ + Type: fftypes.TransactionTypeTokenPool, + ID: txID, + }, + } + event := &blockchain.Event{ + Name: "TokenPool", + } + + mdi.On("InsertBlockchainEvent", em.ctx, mock.MatchedBy(func(e *fftypes.BlockchainEvent) bool { + return e.Name == event.Name + })).Return(fmt.Errorf("pop")) + + err := em.confirmPool(em.ctx, storedPool, event) + assert.EqualError(t, err, "pop") + + mdi.AssertExpectations(t) +} + func TestConfirmPoolTxFail(t *testing.T) { em, cancel := newTestEventManager(t) defer cancel() @@ -242,11 +289,21 @@ func TestConfirmPoolTxFail(t *testing.T) { ID: txID, }, } + event := &blockchain.Event{ + Name: "TokenPool", + } - mdi.On("GetTransactionByID", em.ctx, txID).Return(nil, fmt.Errorf("pop")) + mdi.On("InsertBlockchainEvent", em.ctx, mock.MatchedBy(func(e *fftypes.BlockchainEvent) bool { + return e.Name == event.Name + })).Return(nil) + mdi.On("InsertEvent", em.ctx, mock.MatchedBy(func(e *fftypes.Event) bool { + return e.Type == fftypes.EventTypeBlockchainEvent + })).Return(nil) + mdi.On("UpsertTransaction", em.ctx, mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Type == fftypes.TransactionTypeTokenPool + })).Return(fmt.Errorf("pop")) - info := fftypes.JSONObject{"some": "info"} - err := em.confirmPool(em.ctx, storedPool, "tx1", info) + err := em.confirmPool(em.ctx, storedPool, event) assert.EqualError(t, err, "pop") mdi.AssertExpectations(t) @@ -268,23 +325,22 @@ func TestConfirmPoolUpsertFail(t *testing.T) { ID: txID, }, } - storedTX := &fftypes.Transaction{ - Subject: fftypes.TransactionSubject{ - Namespace: "ns1", - Reference: storedPool.ID, - Signer: storedPool.Key, - Type: fftypes.TransactionTypeTokenPool, - }, + event := &blockchain.Event{ + Name: "TokenPool", } - mdi.On("GetTransactionByID", em.ctx, txID).Return(storedTX, nil) + mdi.On("InsertBlockchainEvent", em.ctx, mock.MatchedBy(func(e *fftypes.BlockchainEvent) bool { + return e.Name == event.Name + })).Return(nil) + mdi.On("InsertEvent", em.ctx, mock.MatchedBy(func(e *fftypes.Event) bool { + return e.Type == fftypes.EventTypeBlockchainEvent + })).Return(nil) mdi.On("UpsertTransaction", em.ctx, mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return *tx.Subject.Reference == *storedTX.Subject.Reference - }), false).Return(nil) + return tx.Type == fftypes.TransactionTypeTokenPool + })).Return(nil) mdi.On("UpsertTokenPool", em.ctx, storedPool).Return(fmt.Errorf("pop")) - info := fftypes.JSONObject{"some": "info"} - err := em.confirmPool(em.ctx, storedPool, "tx1", info) + err := em.confirmPool(em.ctx, storedPool, event) assert.EqualError(t, err, "pop") mdi.AssertExpectations(t) @@ -309,12 +365,17 @@ func TestTokenPoolCreatedAnnounce(t *testing.T) { }, }, } + info := fftypes.JSONObject{"some": "info"} pool := &tokens.TokenPool{ Type: fftypes.TokenTypeFungible, ProtocolID: "123", Key: "0x0", TransactionID: txID, Connector: "erc1155", + Event: blockchain.Event{ + ProtocolID: "tx1", + Info: info, + }, } mti.On("Name").Return("mock-tokens") @@ -328,8 +389,7 @@ func TestTokenPoolCreatedAnnounce(t *testing.T) { return pool.Pool.Namespace == "test-ns" && pool.Pool.Name == "my-pool" && *pool.Pool.ID == *poolID }), false).Return(nil, nil) - info := fftypes.JSONObject{"some": "info"} - err := em.TokenPoolCreated(mti, pool, "tx1", info) + err := em.TokenPoolCreated(mti, pool) assert.NoError(t, err) mti.AssertExpectations(t) @@ -351,19 +411,23 @@ func TestTokenPoolCreatedAnnounceBadOpInputID(t *testing.T) { Input: fftypes.JSONObject{}, }, } + info := fftypes.JSONObject{"some": "info"} pool := &tokens.TokenPool{ Type: fftypes.TokenTypeFungible, ProtocolID: "123", Key: "0x0", TransactionID: txID, Connector: "erc1155", + Event: blockchain.Event{ + ProtocolID: "tx1", + Info: info, + }, } mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "123").Return(nil, nil) mdi.On("GetOperations", em.ctx, mock.Anything).Return(operations, nil, nil) - info := fftypes.JSONObject{"some": "info"} - err := em.TokenPoolCreated(mti, pool, "tx1", info) + err := em.TokenPoolCreated(mti, pool) assert.NoError(t, err) mdi.AssertExpectations(t) @@ -385,19 +449,23 @@ func TestTokenPoolCreatedAnnounceBadOpInputNS(t *testing.T) { }, }, } + info := fftypes.JSONObject{"some": "info"} pool := &tokens.TokenPool{ Type: fftypes.TokenTypeFungible, ProtocolID: "123", Key: "0x0", TransactionID: txID, Connector: "erc1155", + Event: blockchain.Event{ + ProtocolID: "tx1", + Info: info, + }, } mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "123").Return(nil, nil) mdi.On("GetOperations", em.ctx, mock.Anything).Return(operations, nil, nil) - info := fftypes.JSONObject{"some": "info"} - err := em.TokenPoolCreated(mti, pool, "tx1", info) + err := em.TokenPoolCreated(mti, pool) assert.NoError(t, err) mdi.AssertExpectations(t) diff --git a/internal/events/tokens_transferred.go b/internal/events/tokens_transferred.go index d8fe87fdae..1082629acd 100644 --- a/internal/events/tokens_transferred.go +++ b/internal/events/tokens_transferred.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -26,13 +26,13 @@ import ( "github.com/hyperledger/firefly/pkg/tokens" ) -func (em *eventManager) loadTransferOperation(ctx context.Context, transfer *fftypes.TokenTransfer) error { +func (em *eventManager) loadTransferOperation(ctx context.Context, tx *fftypes.UUID, transfer *fftypes.TokenTransfer) error { transfer.LocalID = nil // Find a matching operation within this transaction fb := database.OperationQueryFactory.NewFilter(ctx) filter := fb.And( - fb.Eq("tx", transfer.TX.ID), + fb.Eq("tx", tx), fb.Eq("type", fftypes.OpTypeTokenTransfer), ) operations, _, err := em.database.GetOperations(ctx, filter) @@ -51,75 +51,80 @@ func (em *eventManager) loadTransferOperation(ctx context.Context, transfer *fft return nil } -func (em *eventManager) persistTokenTransaction(ctx context.Context, ns string, transfer *fftypes.TokenTransfer, protocolTxID string, additionalInfo fftypes.JSONObject) (valid bool, err error) { - transaction := &fftypes.Transaction{ - ID: transfer.TX.ID, - Status: fftypes.OpStatusSucceeded, - Subject: fftypes.TransactionSubject{ - Namespace: ns, +func (em *eventManager) persistTokenTransfer(ctx context.Context, transfer *tokens.TokenTransfer) (valid bool, err error) { + // Check that transfer has not already been recorded + if existing, err := em.database.GetTokenTransferByProtocolID(ctx, transfer.Connector, transfer.ProtocolID); err != nil { + return false, err + } else if existing != nil { + log.L(ctx).Warnf("Token transfer '%s' has already been recorded - ignoring", transfer.ProtocolID) + return false, nil + } + + // Check that this is from a known pool + // TODO: should cache this lookup for efficiency + pool, err := em.database.GetTokenPoolByProtocolID(ctx, transfer.Connector, transfer.PoolProtocolID) + if err != nil { + return false, err + } + if pool == nil { + log.L(ctx).Infof("Token transfer received for unknown pool '%s' - ignoring: %s", transfer.PoolProtocolID, transfer.Event.ProtocolID) + return false, nil + } + transfer.Namespace = pool.Namespace + transfer.Pool = pool.ID + + if transfer.TX.ID != nil { + if err := em.loadTransferOperation(ctx, transfer.TX.ID, &transfer.TokenTransfer); err != nil { + return false, err + } + + tx := &fftypes.Transaction{ + ID: transfer.TX.ID, + Status: fftypes.OpStatusSucceeded, + Namespace: transfer.Namespace, Type: transfer.TX.Type, - Signer: transfer.Key, - Reference: transfer.LocalID, - }, - ProtocolID: protocolTxID, - Info: additionalInfo, + } + if err := em.database.UpsertTransaction(ctx, tx); err != nil { + return false, err + } + + // Some operations result in multiple transfer events - if the protocol ID was unique but the + // local ID is not unique, generate a unique local ID now. + if existing, err := em.database.GetTokenTransfer(ctx, transfer.LocalID); err != nil { + return false, err + } else if existing != nil { + transfer.LocalID = fftypes.NewUUID() + } + } else { + transfer.LocalID = fftypes.NewUUID() + } + + chainEvent := buildBlockchainEvent(pool.Namespace, nil, &transfer.Event, &transfer.TX) + transfer.BlockchainEvent = chainEvent.ID + if err := em.persistBlockchainEvent(ctx, chainEvent); err != nil { + return false, err + } + + if err := em.database.UpsertTokenTransfer(ctx, &transfer.TokenTransfer); err != nil { + log.L(ctx).Errorf("Failed to record token transfer '%s': %s", transfer.ProtocolID, err) + return false, err + } + if err := em.database.UpdateTokenBalances(ctx, &transfer.TokenTransfer); err != nil { + log.L(ctx).Errorf("Failed to update accounts %s -> %s for token transfer '%s': %s", transfer.From, transfer.To, transfer.ProtocolID, err) + return false, err } - return em.txhelper.PersistTransaction(ctx, transaction) + log.L(ctx).Infof("Token transfer recorded id=%s author=%s", transfer.ProtocolID, transfer.Key) + return true, nil } -func (em *eventManager) TokensTransferred(ti tokens.Plugin, poolProtocolID string, transfer *fftypes.TokenTransfer, protocolTxID string, additionalInfo fftypes.JSONObject) error { +func (em *eventManager) TokensTransferred(ti tokens.Plugin, transfer *tokens.TokenTransfer) error { var batchID *fftypes.UUID err := em.retry.Do(em.ctx, "persist token transfer", func(attempt int) (bool, error) { err := em.database.RunAsGroup(em.ctx, func(ctx context.Context) error { - // Check that transfer has not already been recorded - if existing, err := em.database.GetTokenTransferByProtocolID(ctx, transfer.Connector, transfer.ProtocolID); err != nil { - return err - } else if existing != nil { - log.L(ctx).Warnf("Token transfer '%s' has already been recorded - ignoring", transfer.ProtocolID) - return nil - } - - // Check that this is from a known pool - pool, err := em.database.GetTokenPoolByProtocolID(ctx, transfer.Connector, poolProtocolID) - if err != nil { - return err - } - if pool == nil { - log.L(ctx).Infof("Token transfer received for unknown pool '%s' - ignoring: %s", poolProtocolID, protocolTxID) - return nil - } - transfer.Namespace = pool.Namespace - transfer.Pool = pool.ID - - if transfer.TX.ID != nil { - if err := em.loadTransferOperation(ctx, transfer); err != nil { - return err - } - if valid, err := em.persistTokenTransaction(ctx, pool.Namespace, transfer, protocolTxID, additionalInfo); err != nil || !valid { - return err - } - - // Some operations result in multiple transfer events - if the protocol ID was unique but the - // local ID is not unique, generate a unique local ID now. - if existing, err := em.database.GetTokenTransfer(ctx, transfer.LocalID); err != nil { - return err - } else if existing != nil { - transfer.LocalID = fftypes.NewUUID() - } - } else { - transfer.LocalID = fftypes.NewUUID() - } - - if err := em.database.UpsertTokenTransfer(ctx, transfer); err != nil { - log.L(ctx).Errorf("Failed to record token transfer '%s': %s", transfer.ProtocolID, err) - return err - } - if err := em.database.UpdateTokenBalances(ctx, transfer); err != nil { - log.L(ctx).Errorf("Failed to update accounts %s -> %s for token transfer '%s': %s", transfer.From, transfer.To, transfer.ProtocolID, err) + if valid, err := em.persistTokenTransfer(ctx, transfer); !valid || err != nil { return err } - log.L(ctx).Infof("Token transfer recorded id=%s author=%s", transfer.ProtocolID, transfer.Key) if transfer.Message != nil { if msg, err := em.database.GetMessageByID(ctx, transfer.Message); err != nil { @@ -138,7 +143,7 @@ func (em *eventManager) TokensTransferred(ti tokens.Plugin, poolProtocolID strin } } - event := fftypes.NewEvent(fftypes.EventTypeTransferConfirmed, pool.Namespace, transfer.LocalID) + event := fftypes.NewEvent(fftypes.EventTypeTransferConfirmed, transfer.Namespace, transfer.LocalID) return em.database.InsertEvent(ctx, event) }) return err != nil, err // retry indefinitely (until context closes) diff --git a/internal/events/tokens_transferred_test.go b/internal/events/tokens_transferred_test.go index 406bd81ab2..a990ab8e03 100644 --- a/internal/events/tokens_transferred_test.go +++ b/internal/events/tokens_transferred_test.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -22,12 +22,40 @@ import ( "github.com/hyperledger/firefly/mocks/databasemocks" "github.com/hyperledger/firefly/mocks/tokenmocks" + "github.com/hyperledger/firefly/pkg/blockchain" "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" + "github.com/hyperledger/firefly/pkg/tokens" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) +func newTransfer() *tokens.TokenTransfer { + return &tokens.TokenTransfer{ + PoolProtocolID: "F1", + TokenTransfer: fftypes.TokenTransfer{ + Type: fftypes.TokenTransferTypeTransfer, + TokenIndex: "0", + Connector: "erc1155", + Key: "0x12345", + From: "0x1", + To: "0x2", + ProtocolID: "123", + URI: "firefly://token/1", + Amount: *fftypes.NewFFBigInt(1), + }, + TX: fftypes.TransactionRef{ + ID: fftypes.NewUUID(), + Type: fftypes.TransactionTypeTokenTransfer, + }, + Event: blockchain.Event{ + Name: "Transfer", + ProtocolID: "tx1", + Info: fftypes.JSONObject{"some": "info"}, + }, + } +} + func TestTokensTransferredSucceedWithRetries(t *testing.T) { em, cancel := newTestEventManager(t) defer cancel() @@ -35,18 +63,8 @@ func TestTokensTransferredSucceedWithRetries(t *testing.T) { mdi := em.database.(*databasemocks.Plugin) mti := &tokenmocks.Plugin{} - uri := "firefly://token/1" - transfer := &fftypes.TokenTransfer{ - Type: fftypes.TokenTransferTypeTransfer, - TokenIndex: "0", - URI: uri, - Connector: "erc1155", - Key: "0x12345", - From: "0x1", - To: "0x2", - ProtocolID: "123", - Amount: *fftypes.NewFFBigInt(1), - } + transfer := newTransfer() + transfer.TX = fftypes.TransactionRef{} pool := &fftypes.TokenPool{ Namespace: "ns1", } @@ -55,16 +73,21 @@ func TestTokensTransferredSucceedWithRetries(t *testing.T) { mdi.On("GetTokenTransferByProtocolID", em.ctx, "erc1155", "123").Return(nil, nil).Times(4) mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "F1").Return(nil, fmt.Errorf("pop")).Once() mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "F1").Return(pool, nil).Times(3) - mdi.On("UpsertTokenTransfer", em.ctx, transfer).Return(fmt.Errorf("pop")).Once() - mdi.On("UpsertTokenTransfer", em.ctx, transfer).Return(nil).Times(2) - mdi.On("UpdateTokenBalances", em.ctx, transfer).Return(fmt.Errorf("pop")).Once() - mdi.On("UpdateTokenBalances", em.ctx, transfer).Return(nil).Once() + mdi.On("InsertBlockchainEvent", em.ctx, mock.MatchedBy(func(e *fftypes.BlockchainEvent) bool { + return e.Namespace == pool.Namespace && e.Name == transfer.Event.Name + })).Return(nil).Times(3) + mdi.On("InsertEvent", em.ctx, mock.MatchedBy(func(ev *fftypes.Event) bool { + return ev.Type == fftypes.EventTypeBlockchainEvent && ev.Namespace == pool.Namespace + })).Return(nil).Times(3) + mdi.On("UpsertTokenTransfer", em.ctx, &transfer.TokenTransfer).Return(fmt.Errorf("pop")).Once() + mdi.On("UpsertTokenTransfer", em.ctx, &transfer.TokenTransfer).Return(nil).Times(2) + mdi.On("UpdateTokenBalances", em.ctx, &transfer.TokenTransfer).Return(fmt.Errorf("pop")).Once() + mdi.On("UpdateTokenBalances", em.ctx, &transfer.TokenTransfer).Return(nil).Once() mdi.On("InsertEvent", em.ctx, mock.MatchedBy(func(ev *fftypes.Event) bool { return ev.Type == fftypes.EventTypeTransferConfirmed && ev.Reference == transfer.LocalID && ev.Namespace == pool.Namespace })).Return(nil).Once() - info := fftypes.JSONObject{"some": "info"} - err := em.TokensTransferred(mti, "F1", transfer, "tx1", info) + err := em.TokensTransferred(mti, transfer) assert.NoError(t, err) mdi.AssertExpectations(t) @@ -78,135 +101,165 @@ func TestTokensTransferredIgnoreExisting(t *testing.T) { mdi := em.database.(*databasemocks.Plugin) mti := &tokenmocks.Plugin{} - transfer := &fftypes.TokenTransfer{ - Type: fftypes.TokenTransferTypeTransfer, - TokenIndex: "0", - Connector: "erc1155", - Key: "0x12345", - From: "0x1", - To: "0x2", - ProtocolID: "123", - Amount: *fftypes.NewFFBigInt(1), - } + transfer := newTransfer() mdi.On("GetTokenTransferByProtocolID", em.ctx, "erc1155", "123").Return(&fftypes.TokenTransfer{}, nil) - info := fftypes.JSONObject{"some": "info"} - err := em.TokensTransferred(mti, "F1", transfer, "tx1", info) + err := em.TokensTransferred(mti, transfer) assert.NoError(t, err) mdi.AssertExpectations(t) mti.AssertExpectations(t) } -func TestTokensTransferredWithTransactionRetries(t *testing.T) { +func TestPersistTransferOpFail(t *testing.T) { em, cancel := newTestEventManager(t) defer cancel() mdi := em.database.(*databasemocks.Plugin) - mti := &tokenmocks.Plugin{} - transfer := &fftypes.TokenTransfer{ - Type: fftypes.TokenTransferTypeTransfer, - TokenIndex: "0", - Connector: "erc1155", - Key: "0x12345", - From: "0x1", - To: "0x2", - ProtocolID: "123", - Amount: *fftypes.NewFFBigInt(1), - TX: fftypes.TransactionRef{ - ID: fftypes.NewUUID(), - Type: fftypes.TransactionTypeTokenTransfer, - }, + transfer := newTransfer() + pool := &fftypes.TokenPool{ + Namespace: "ns1", } + + mdi.On("GetTokenTransferByProtocolID", em.ctx, "erc1155", "123").Return(nil, nil) + mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "F1").Return(pool, nil) + mdi.On("GetOperations", em.ctx, mock.Anything).Return(nil, nil, fmt.Errorf("pop")) + + valid, err := em.persistTokenTransfer(em.ctx, transfer) + assert.False(t, valid) + assert.EqualError(t, err, "pop") + + mdi.AssertExpectations(t) +} + +func TestPersistTransferBadOp(t *testing.T) { + em, cancel := newTestEventManager(t) + defer cancel() + + mdi := em.database.(*databasemocks.Plugin) + + transfer := newTransfer() pool := &fftypes.TokenPool{ Namespace: "ns1", } - operationsBad := []*fftypes.Operation{{ + ops := []*fftypes.Operation{{ Input: fftypes.JSONObject{ "id": "bad", }, }} - operationsGood := []*fftypes.Operation{{ + + mdi.On("GetTokenTransferByProtocolID", em.ctx, "erc1155", "123").Return(nil, nil) + mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "F1").Return(pool, nil) + mdi.On("GetOperations", em.ctx, mock.Anything).Return(ops, nil, nil) + mdi.On("UpsertTransaction", em.ctx, mock.MatchedBy(func(t *fftypes.Transaction) bool { + return *t.ID == *transfer.TX.ID && t.Type == fftypes.TransactionTypeTokenTransfer + })).Return(fmt.Errorf("pop")) + + valid, err := em.persistTokenTransfer(em.ctx, transfer) + assert.False(t, valid) + assert.EqualError(t, err, "pop") + + mdi.AssertExpectations(t) +} + +func TestPersistTransferTxFail(t *testing.T) { + em, cancel := newTestEventManager(t) + defer cancel() + + mdi := em.database.(*databasemocks.Plugin) + + transfer := newTransfer() + pool := &fftypes.TokenPool{ + Namespace: "ns1", + } + localID := fftypes.NewUUID() + ops := []*fftypes.Operation{{ Input: fftypes.JSONObject{ - "id": fftypes.NewUUID().String(), + "id": localID.String(), }, }} - mdi.On("GetTokenTransferByProtocolID", em.ctx, "erc1155", "123").Return(nil, nil).Times(3) - mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "F1").Return(pool, nil).Times(3) - mdi.On("GetOperations", em.ctx, mock.Anything).Return(nil, nil, fmt.Errorf("pop")).Once() - mdi.On("GetOperations", em.ctx, mock.Anything).Return(operationsBad, nil, nil).Once() - mdi.On("GetOperations", em.ctx, mock.Anything).Return(operationsGood, nil, nil).Once() - mdi.On("GetTransactionByID", em.ctx, transfer.TX.ID).Return(nil, fmt.Errorf("pop")).Once() - mdi.On("GetTransactionByID", em.ctx, transfer.TX.ID).Return(nil, nil).Once() + mdi.On("GetTokenTransferByProtocolID", em.ctx, "erc1155", "123").Return(nil, nil) + mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "F1").Return(pool, nil) + mdi.On("GetOperations", em.ctx, mock.Anything).Return(ops, nil, nil) mdi.On("UpsertTransaction", em.ctx, mock.MatchedBy(func(t *fftypes.Transaction) bool { - return *t.ID == *transfer.TX.ID && t.Subject.Type == fftypes.TransactionTypeTokenTransfer && t.ProtocolID == "tx1" - }), false).Return(database.HashMismatch).Once() + return *t.ID == *transfer.TX.ID && t.Type == fftypes.TransactionTypeTokenTransfer + })).Return(fmt.Errorf("pop")) - info := fftypes.JSONObject{"some": "info"} - err := em.TokensTransferred(mti, "F1", transfer, "tx1", info) - assert.NoError(t, err) + valid, err := em.persistTokenTransfer(em.ctx, transfer) + assert.False(t, valid) + assert.EqualError(t, err, "pop") mdi.AssertExpectations(t) - mti.AssertExpectations(t) } -func TestTokensTransferredWithTransactionLoadLocalID(t *testing.T) { +func TestPersistTransferGetTransferFail(t *testing.T) { em, cancel := newTestEventManager(t) defer cancel() mdi := em.database.(*databasemocks.Plugin) - mti := &tokenmocks.Plugin{} - transfer := &fftypes.TokenTransfer{ - Type: fftypes.TokenTransferTypeTransfer, - TokenIndex: "0", - Connector: "erc1155", - Key: "0x12345", - From: "0x1", - To: "0x2", - ProtocolID: "123", - Amount: *fftypes.NewFFBigInt(1), - TX: fftypes.TransactionRef{ - ID: fftypes.NewUUID(), - Type: fftypes.TransactionTypeTokenTransfer, - }, - } + transfer := newTransfer() pool := &fftypes.TokenPool{ Namespace: "ns1", } localID := fftypes.NewUUID() - operations := []*fftypes.Operation{{ + ops := []*fftypes.Operation{{ Input: fftypes.JSONObject{ "id": localID.String(), }, }} - mdi.On("GetTokenTransferByProtocolID", em.ctx, "erc1155", "123").Return(nil, nil).Times(2) - mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "F1").Return(pool, nil).Times(2) - mdi.On("GetOperations", em.ctx, mock.Anything).Return(operations, nil, nil).Times(2) - mdi.On("GetTransactionByID", em.ctx, transfer.TX.ID).Return(nil, nil).Times(2) + mdi.On("GetTokenTransferByProtocolID", em.ctx, "erc1155", "123").Return(nil, nil) + mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "F1").Return(pool, nil) + mdi.On("GetOperations", em.ctx, mock.Anything).Return(ops, nil, nil) mdi.On("UpsertTransaction", em.ctx, mock.MatchedBy(func(t *fftypes.Transaction) bool { - return *t.ID == *transfer.TX.ID && t.Subject.Type == fftypes.TransactionTypeTokenTransfer && t.ProtocolID == "tx1" - }), false).Return(nil).Times(2) - mdi.On("GetTokenTransfer", em.ctx, localID).Return(nil, fmt.Errorf("pop")).Once() - mdi.On("GetTokenTransfer", em.ctx, localID).Return(nil, nil).Once() - mdi.On("UpsertTokenTransfer", em.ctx, transfer).Return(nil).Once() - mdi.On("UpdateTokenBalances", em.ctx, transfer).Return(nil).Once() - mdi.On("InsertEvent", em.ctx, mock.MatchedBy(func(ev *fftypes.Event) bool { - return ev.Type == fftypes.EventTypeTransferConfirmed && ev.Reference == transfer.LocalID && ev.Namespace == pool.Namespace - })).Return(nil).Once() + return *t.ID == *transfer.TX.ID && t.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) + mdi.On("GetTokenTransfer", em.ctx, localID).Return(nil, fmt.Errorf("pop")) - info := fftypes.JSONObject{"some": "info"} - err := em.TokensTransferred(mti, "F1", transfer, "tx1", info) - assert.NoError(t, err) + valid, err := em.persistTokenTransfer(em.ctx, transfer) + assert.False(t, valid) + assert.EqualError(t, err, "pop") - assert.Equal(t, *localID, *transfer.LocalID) + mdi.AssertExpectations(t) +} + +func TestPersistTransferBlockchainEventFail(t *testing.T) { + em, cancel := newTestEventManager(t) + defer cancel() + + mdi := em.database.(*databasemocks.Plugin) + + transfer := newTransfer() + pool := &fftypes.TokenPool{ + Namespace: "ns1", + } + localID := fftypes.NewUUID() + ops := []*fftypes.Operation{{ + Input: fftypes.JSONObject{ + "id": localID.String(), + }, + }} + + mdi.On("GetTokenTransferByProtocolID", em.ctx, "erc1155", "123").Return(nil, nil) + mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "F1").Return(pool, nil) + mdi.On("GetOperations", em.ctx, mock.Anything).Return(ops, nil, nil) + mdi.On("UpsertTransaction", em.ctx, mock.MatchedBy(func(t *fftypes.Transaction) bool { + return *t.ID == *transfer.TX.ID && t.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) + mdi.On("GetTokenTransfer", em.ctx, localID).Return(nil, nil) + mdi.On("InsertBlockchainEvent", em.ctx, mock.MatchedBy(func(e *fftypes.BlockchainEvent) bool { + return e.Namespace == pool.Namespace && e.Name == transfer.Event.Name + })).Return(fmt.Errorf("pop")) + + valid, err := em.persistTokenTransfer(em.ctx, transfer) + assert.False(t, valid) + assert.EqualError(t, err, "pop") mdi.AssertExpectations(t) - mti.AssertExpectations(t) } func TestTokensTransferredWithTransactionRegenerateLocalID(t *testing.T) { @@ -216,20 +269,7 @@ func TestTokensTransferredWithTransactionRegenerateLocalID(t *testing.T) { mdi := em.database.(*databasemocks.Plugin) mti := &tokenmocks.Plugin{} - transfer := &fftypes.TokenTransfer{ - Type: fftypes.TokenTransferTypeTransfer, - TokenIndex: "0", - Connector: "erc1155", - Key: "0x12345", - From: "0x1", - To: "0x2", - ProtocolID: "123", - Amount: *fftypes.NewFFBigInt(1), - TX: fftypes.TransactionRef{ - ID: fftypes.NewUUID(), - Type: fftypes.TransactionTypeTokenTransfer, - }, - } + transfer := newTransfer() pool := &fftypes.TokenPool{ Namespace: "ns1", } @@ -240,22 +280,24 @@ func TestTokensTransferredWithTransactionRegenerateLocalID(t *testing.T) { }, }} - mdi.On("GetTokenTransferByProtocolID", em.ctx, "erc1155", "123").Return(nil, nil).Once() - mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "F1").Return(pool, nil).Once() - mdi.On("GetOperations", em.ctx, mock.Anything).Return(operations, nil, nil).Once() - mdi.On("GetTransactionByID", em.ctx, transfer.TX.ID).Return(nil, nil).Once() + mdi.On("GetTokenTransferByProtocolID", em.ctx, "erc1155", "123").Return(nil, nil) + mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "F1").Return(pool, nil) + mdi.On("GetOperations", em.ctx, mock.Anything).Return(operations, nil, nil) mdi.On("UpsertTransaction", em.ctx, mock.MatchedBy(func(t *fftypes.Transaction) bool { - return *t.ID == *transfer.TX.ID && t.Subject.Type == fftypes.TransactionTypeTokenTransfer && t.ProtocolID == "tx1" - }), false).Return(nil).Once() - mdi.On("GetTokenTransfer", em.ctx, localID).Return(&fftypes.TokenTransfer{}, nil).Once() - mdi.On("UpsertTokenTransfer", em.ctx, transfer).Return(nil).Once() - mdi.On("UpdateTokenBalances", em.ctx, transfer).Return(nil).Once() + return *t.ID == *transfer.TX.ID && t.Type == fftypes.TransactionTypeTokenTransfer + })).Return(nil) + mdi.On("GetTokenTransfer", em.ctx, localID).Return(&fftypes.TokenTransfer{}, nil) + mdi.On("InsertBlockchainEvent", em.ctx, mock.MatchedBy(func(e *fftypes.BlockchainEvent) bool { + return e.Namespace == pool.Namespace && e.Name == transfer.Event.Name + })).Return(nil) mdi.On("InsertEvent", em.ctx, mock.MatchedBy(func(ev *fftypes.Event) bool { - return ev.Type == fftypes.EventTypeTransferConfirmed && ev.Reference == transfer.LocalID && ev.Namespace == pool.Namespace - })).Return(nil).Once() + return ev.Type == fftypes.EventTypeBlockchainEvent && ev.Namespace == pool.Namespace + })).Return(nil) + mdi.On("UpsertTokenTransfer", em.ctx, &transfer.TokenTransfer).Return(nil) + mdi.On("UpdateTokenBalances", em.ctx, &transfer.TokenTransfer).Return(nil) - info := fftypes.JSONObject{"some": "info"} - err := em.TokensTransferred(mti, "F1", transfer, "tx1", info) + valid, err := em.persistTokenTransfer(em.ctx, transfer) + assert.True(t, valid) assert.NoError(t, err) assert.NotEqual(t, *localID, *transfer.LocalID) @@ -271,22 +313,12 @@ func TestTokensTransferredBadPool(t *testing.T) { mdi := em.database.(*databasemocks.Plugin) mti := &tokenmocks.Plugin{} - transfer := &fftypes.TokenTransfer{ - Type: fftypes.TokenTransferTypeTransfer, - TokenIndex: "0", - Connector: "erc1155", - Key: "0x12345", - From: "0x1", - To: "0x2", - ProtocolID: "123", - Amount: *fftypes.NewFFBigInt(1), - } + transfer := newTransfer() mdi.On("GetTokenTransferByProtocolID", em.ctx, "erc1155", "123").Return(nil, nil) mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "F1").Return(nil, nil) - info := fftypes.JSONObject{"some": "info"} - err := em.TokensTransferred(mti, "F1", transfer, "tx1", info) + err := em.TokensTransferred(mti, transfer) assert.NoError(t, err) mdi.AssertExpectations(t) @@ -301,17 +333,25 @@ func TestTokensTransferredWithMessageReceived(t *testing.T) { mti := &tokenmocks.Plugin{} uri := "firefly://token/1" - transfer := &fftypes.TokenTransfer{ - Type: fftypes.TokenTransferTypeTransfer, - TokenIndex: "0", - URI: uri, - Connector: "erc1155", - Key: "0x12345", - From: "0x1", - To: "0x2", - ProtocolID: "123", - Message: fftypes.NewUUID(), - Amount: *fftypes.NewFFBigInt(1), + info := fftypes.JSONObject{"some": "info"} + transfer := &tokens.TokenTransfer{ + PoolProtocolID: "F1", + TokenTransfer: fftypes.TokenTransfer{ + Type: fftypes.TokenTransferTypeTransfer, + TokenIndex: "0", + URI: uri, + Connector: "erc1155", + Key: "0x12345", + From: "0x1", + To: "0x2", + ProtocolID: "123", + Message: fftypes.NewUUID(), + Amount: *fftypes.NewFFBigInt(1), + }, + Event: blockchain.Event{ + ProtocolID: "tx1", + Info: info, + }, } pool := &fftypes.TokenPool{ Namespace: "ns1", @@ -322,16 +362,21 @@ func TestTokensTransferredWithMessageReceived(t *testing.T) { mdi.On("GetTokenTransferByProtocolID", em.ctx, "erc1155", "123").Return(nil, nil).Times(2) mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "F1").Return(pool, nil).Times(2) - mdi.On("UpsertTokenTransfer", em.ctx, transfer).Return(nil).Times(2) - mdi.On("UpdateTokenBalances", em.ctx, transfer).Return(nil).Times(2) + mdi.On("InsertBlockchainEvent", em.ctx, mock.MatchedBy(func(e *fftypes.BlockchainEvent) bool { + return e.Namespace == pool.Namespace && e.Name == transfer.Event.Name + })).Return(nil).Times(2) + mdi.On("InsertEvent", em.ctx, mock.MatchedBy(func(ev *fftypes.Event) bool { + return ev.Type == fftypes.EventTypeBlockchainEvent && ev.Namespace == pool.Namespace + })).Return(nil).Times(2) + mdi.On("UpsertTokenTransfer", em.ctx, &transfer.TokenTransfer).Return(nil).Times(2) + mdi.On("UpdateTokenBalances", em.ctx, &transfer.TokenTransfer).Return(nil).Times(2) mdi.On("GetMessageByID", em.ctx, transfer.Message).Return(nil, fmt.Errorf("pop")).Once() mdi.On("GetMessageByID", em.ctx, transfer.Message).Return(message, nil).Once() mdi.On("InsertEvent", em.ctx, mock.MatchedBy(func(ev *fftypes.Event) bool { return ev.Type == fftypes.EventTypeTransferConfirmed && ev.Reference == transfer.LocalID && ev.Namespace == pool.Namespace })).Return(nil).Once() - info := fftypes.JSONObject{"some": "info"} - err := em.TokensTransferred(mti, "F1", transfer, "tx1", info) + err := em.TokensTransferred(mti, transfer) assert.NoError(t, err) mdi.AssertExpectations(t) @@ -346,17 +391,25 @@ func TestTokensTransferredWithMessageSend(t *testing.T) { mti := &tokenmocks.Plugin{} uri := "firefly://token/1" - transfer := &fftypes.TokenTransfer{ - Type: fftypes.TokenTransferTypeTransfer, - TokenIndex: "0", - URI: uri, - Connector: "erc1155", - Key: "0x12345", - From: "0x1", - To: "0x2", - ProtocolID: "123", - Message: fftypes.NewUUID(), - Amount: *fftypes.NewFFBigInt(1), + info := fftypes.JSONObject{"some": "info"} + transfer := &tokens.TokenTransfer{ + PoolProtocolID: "F1", + TokenTransfer: fftypes.TokenTransfer{ + Type: fftypes.TokenTransferTypeTransfer, + TokenIndex: "0", + URI: uri, + Connector: "erc1155", + Key: "0x12345", + From: "0x1", + To: "0x2", + ProtocolID: "123", + Message: fftypes.NewUUID(), + Amount: *fftypes.NewFFBigInt(1), + }, + Event: blockchain.Event{ + ProtocolID: "tx1", + Info: info, + }, } pool := &fftypes.TokenPool{ Namespace: "ns1", @@ -368,8 +421,14 @@ func TestTokensTransferredWithMessageSend(t *testing.T) { mdi.On("GetTokenTransferByProtocolID", em.ctx, "erc1155", "123").Return(nil, nil).Times(2) mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "F1").Return(pool, nil).Times(2) - mdi.On("UpsertTokenTransfer", em.ctx, transfer).Return(nil).Times(2) - mdi.On("UpdateTokenBalances", em.ctx, transfer).Return(nil).Times(2) + mdi.On("InsertBlockchainEvent", em.ctx, mock.MatchedBy(func(e *fftypes.BlockchainEvent) bool { + return e.Namespace == pool.Namespace && e.Name == transfer.Event.Name + })).Return(nil).Times(2) + mdi.On("InsertEvent", em.ctx, mock.MatchedBy(func(ev *fftypes.Event) bool { + return ev.Type == fftypes.EventTypeBlockchainEvent && ev.Namespace == pool.Namespace + })).Return(nil).Times(2) + mdi.On("UpsertTokenTransfer", em.ctx, &transfer.TokenTransfer).Return(nil).Times(2) + mdi.On("UpdateTokenBalances", em.ctx, &transfer.TokenTransfer).Return(nil).Times(2) mdi.On("GetMessageByID", em.ctx, mock.Anything).Return(message, nil).Times(2) mdi.On("UpsertMessage", em.ctx, mock.Anything, database.UpsertOptimizationExisting).Return(fmt.Errorf("pop")) mdi.On("UpsertMessage", em.ctx, mock.MatchedBy(func(msg *fftypes.Message) bool { @@ -379,8 +438,7 @@ func TestTokensTransferredWithMessageSend(t *testing.T) { return ev.Type == fftypes.EventTypeTransferConfirmed && ev.Reference == transfer.LocalID && ev.Namespace == pool.Namespace })).Return(nil).Once() - info := fftypes.JSONObject{"some": "info"} - err := em.TokensTransferred(mti, "F1", transfer, "tx1", info) + err := em.TokensTransferred(mti, transfer) assert.NoError(t, err) mdi.AssertExpectations(t) diff --git a/internal/orchestrator/bound_callbacks.go b/internal/orchestrator/bound_callbacks.go index bac2e6993e..855bf0f762 100644 --- a/internal/orchestrator/bound_callbacks.go +++ b/internal/orchestrator/bound_callbacks.go @@ -38,8 +38,8 @@ func (bc *boundCallbacks) TokenOpUpdate(plugin tokens.Plugin, operationID *fftyp return bc.ei.OperationUpdate(plugin, operationID, txState, errorMessage, opOutput) } -func (bc *boundCallbacks) BatchPinComplete(batch *blockchain.BatchPin, author string, protocolTxID string, additionalInfo fftypes.JSONObject) error { - return bc.ei.BatchPinComplete(bc.bi, batch, author, protocolTxID, additionalInfo) +func (bc *boundCallbacks) BatchPinComplete(batch *blockchain.BatchPin, signingIdentity string) error { + return bc.ei.BatchPinComplete(bc.bi, batch, signingIdentity) } func (bc *boundCallbacks) TransferResult(trackingID string, status fftypes.OpStatus, update fftypes.TransportStatusUpdate) error { @@ -54,12 +54,12 @@ func (bc *boundCallbacks) MessageReceived(peerID string, data []byte) (manifest return bc.ei.MessageReceived(bc.dx, peerID, data) } -func (bc *boundCallbacks) TokenPoolCreated(plugin tokens.Plugin, pool *tokens.TokenPool, protocolTxID string, additionalInfo fftypes.JSONObject) error { - return bc.ei.TokenPoolCreated(plugin, pool, protocolTxID, additionalInfo) +func (bc *boundCallbacks) TokenPoolCreated(plugin tokens.Plugin, pool *tokens.TokenPool) error { + return bc.ei.TokenPoolCreated(plugin, pool) } -func (bc *boundCallbacks) TokensTransferred(plugin tokens.Plugin, poolProtocolID string, transfer *fftypes.TokenTransfer, protocolTxID string, additionalInfo fftypes.JSONObject) error { - return bc.ei.TokensTransferred(plugin, poolProtocolID, transfer, protocolTxID, additionalInfo) +func (bc *boundCallbacks) TokensTransferred(plugin tokens.Plugin, transfer *tokens.TokenTransfer) error { + return bc.ei.TokensTransferred(plugin, transfer) } func (bc *boundCallbacks) ContractEvent(event *blockchain.ContractEvent) error { diff --git a/internal/orchestrator/bound_callbacks_test.go b/internal/orchestrator/bound_callbacks_test.go index 0f9766c07d..e859634762 100644 --- a/internal/orchestrator/bound_callbacks_test.go +++ b/internal/orchestrator/bound_callbacks_test.go @@ -41,12 +41,12 @@ func TestBoundCallbacks(t *testing.T) { info := fftypes.JSONObject{"hello": "world"} batch := &blockchain.BatchPin{TransactionID: fftypes.NewUUID()} pool := &tokens.TokenPool{} - transfer := &fftypes.TokenTransfer{} + transfer := &tokens.TokenTransfer{} hash := fftypes.NewRandB32() opID := fftypes.NewUUID() - mei.On("BatchPinComplete", mbi, batch, "0x12345", "tx12345", info).Return(fmt.Errorf("pop")) - err := bc.BatchPinComplete(batch, "0x12345", "tx12345", info) + mei.On("BatchPinComplete", mbi, batch, "0x12345").Return(fmt.Errorf("pop")) + err := bc.BatchPinComplete(batch, "0x12345") assert.EqualError(t, err, "pop") mei.On("OperationUpdate", mbi, opID, fftypes.OpStatusFailed, "error info", info).Return(fmt.Errorf("pop")) @@ -71,12 +71,12 @@ func TestBoundCallbacks(t *testing.T) { _, err = bc.MessageReceived("peer1", []byte{}) assert.EqualError(t, err, "pop") - mei.On("TokenPoolCreated", mti, pool, "tx12345", info).Return(fmt.Errorf("pop")) - err = bc.TokenPoolCreated(mti, pool, "tx12345", info) + mei.On("TokenPoolCreated", mti, pool).Return(fmt.Errorf("pop")) + err = bc.TokenPoolCreated(mti, pool) assert.EqualError(t, err, "pop") - mei.On("TokensTransferred", mti, "N1", transfer, "tx12345", info).Return(fmt.Errorf("pop")) - err = bc.TokensTransferred(mti, "N1", transfer, "tx12345", info) + mei.On("TokensTransferred", mti, transfer).Return(fmt.Errorf("pop")) + err = bc.TokensTransferred(mti, transfer) assert.EqualError(t, err, "pop") mei.On("ContractEvent", mock.AnythingOfType("*blockchain.ContractEvent")).Return(fmt.Errorf("pop")) diff --git a/internal/tokens/fftokens/fftokens.go b/internal/tokens/fftokens/fftokens.go index e39dc3516f..ba24ae40ec 100644 --- a/internal/tokens/fftokens/fftokens.go +++ b/internal/tokens/fftokens/fftokens.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -26,6 +26,7 @@ import ( "github.com/hyperledger/firefly/internal/i18n" "github.com/hyperledger/firefly/internal/log" "github.com/hyperledger/firefly/internal/restclient" + "github.com/hyperledger/firefly/pkg/blockchain" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/hyperledger/firefly/pkg/tokens" "github.com/hyperledger/firefly/pkg/wsclient" @@ -156,7 +157,7 @@ func (ft *FFTokens) handleReceipt(ctx context.Context, data fftypes.JSONObject) l.Errorf("Reply cannot be processed - missing fields: %+v", data) return nil // Swallow this and move on } - operationID, err := fftypes.ParseUUID(ctx, requestID) + opID, err := fftypes.ParseUUID(ctx, requestID) if err != nil { l.Errorf("Reply cannot be processed - bad ID: %+v", data) return nil // Swallow this and move on @@ -166,7 +167,7 @@ func (ft *FFTokens) handleReceipt(ctx context.Context, data fftypes.JSONObject) replyType = fftypes.OpStatusFailed } l.Infof("Tokens '%s' reply: request=%s message=%s", replyType, requestID, message) - return ft.callbacks.TokenOpUpdate(ft, operationID, replyType, message, data) + return ft.callbacks.TokenOpUpdate(ft, opID, replyType, message, data) } func (ft *FFTokens) handleTokenPoolCreate(ctx context.Context, data fftypes.JSONObject) (err error) { @@ -176,6 +177,13 @@ func (ft *FFTokens) handleTokenPoolCreate(ctx context.Context, data fftypes.JSON operatorAddress := data.GetString("operator") tx := data.GetObject("transaction") txHash := tx.GetString("transactionHash") + rawOutput := data.GetObject("rawOutput") // optional + + timestampStr := data.GetString("timestamp") + timestamp, err := fftypes.ParseTimeString(timestampStr) + if err != nil { + timestamp = fftypes.Now() + } if tokenType == "" || protocolID == "" || @@ -201,10 +209,18 @@ func (ft *FFTokens) handleTokenPoolCreate(ctx context.Context, data fftypes.JSON Key: operatorAddress, Connector: ft.configuredName, Standard: standard, + Event: blockchain.Event{ + Source: ft.Name() + ":" + ft.configuredName, + Name: "TokenPool", + ProtocolID: txHash, + Output: rawOutput, + Info: tx, + Timestamp: timestamp, + }, } // If there's an error dispatching the event, we must return the error and shutdown - return ft.callbacks.TokenPoolCreated(ft, pool, txHash, tx) + return ft.callbacks.TokenPoolCreated(ft, pool) } func (ft *FFTokens) handleTokenTransfer(ctx context.Context, t fftypes.TokenTransferType, data fftypes.JSONObject) (err error) { @@ -218,6 +234,13 @@ func (ft *FFTokens) handleTokenTransfer(ctx context.Context, t fftypes.TokenTran txHash := tx.GetString("transactionHash") tokenIndex := data.GetString("tokenIndex") // optional uri := data.GetString("uri") // optional + rawOutput := data.GetObject("rawOutput") // optional + + timestampStr := data.GetString("timestamp") + timestamp, err := fftypes.ParseTimeString(timestampStr) + if err != nil { + timestamp = fftypes.Now() + } var eventName string switch t { @@ -249,31 +272,44 @@ func (ft *FFTokens) handleTokenTransfer(ctx context.Context, t fftypes.TokenTran transferData = tokenData{} } - transfer := &fftypes.TokenTransfer{ - Type: t, - TokenIndex: tokenIndex, - URI: uri, - Connector: ft.configuredName, - From: fromAddress, - To: toAddress, - ProtocolID: protocolID, - Key: operatorAddress, - Message: transferData.Message, - MessageHash: transferData.MessageHash, + var amount fftypes.FFBigInt + _, ok := amount.Int().SetString(value, 10) + if !ok { + log.L(ctx).Errorf("%s event is not valid - invalid amount: %+v", eventName, data) + return nil // move on + } + + transfer := &tokens.TokenTransfer{ + PoolProtocolID: poolProtocolID, + TokenTransfer: fftypes.TokenTransfer{ + Type: t, + TokenIndex: tokenIndex, + URI: uri, + Connector: ft.configuredName, + From: fromAddress, + To: toAddress, + Amount: amount, + ProtocolID: protocolID, + Key: operatorAddress, + Message: transferData.Message, + MessageHash: transferData.MessageHash, + }, TX: fftypes.TransactionRef{ ID: transferData.TX, Type: fftypes.TransactionTypeTokenTransfer, }, - } - - _, ok := transfer.Amount.Int().SetString(value, 10) - if !ok { - log.L(ctx).Errorf("%s event is not valid - invalid amount: %+v", eventName, data) - return nil // move on + Event: blockchain.Event{ + Source: ft.Name() + ":" + ft.configuredName, + Name: eventName, + ProtocolID: txHash, + Output: rawOutput, + Info: tx, + Timestamp: timestamp, + }, } // If there's an error dispatching the event, we must return the error and shutdown - return ft.callbacks.TokensTransferred(ft, poolProtocolID, transfer, txHash, tx) + return ft.callbacks.TokensTransferred(ft, transfer) } func (ft *FFTokens) eventLoop() { @@ -332,14 +368,14 @@ func (ft *FFTokens) eventLoop() { } } -func (ft *FFTokens) CreateTokenPool(ctx context.Context, operationID *fftypes.UUID, pool *fftypes.TokenPool) error { +func (ft *FFTokens) CreateTokenPool(ctx context.Context, opID *fftypes.UUID, pool *fftypes.TokenPool) error { data, _ := json.Marshal(tokenData{ TX: pool.TX.ID, }) res, err := ft.client.R().SetContext(ctx). SetBody(&createPool{ Type: pool.Type, - RequestID: operationID.String(), + RequestID: opID.String(), Operator: pool.Key, Data: string(data), Config: pool.Config, @@ -351,12 +387,12 @@ func (ft *FFTokens) CreateTokenPool(ctx context.Context, operationID *fftypes.UU return nil } -func (ft *FFTokens) ActivateTokenPool(ctx context.Context, operationID *fftypes.UUID, pool *fftypes.TokenPool, tx *fftypes.Transaction) error { +func (ft *FFTokens) ActivateTokenPool(ctx context.Context, opID *fftypes.UUID, pool *fftypes.TokenPool, event *fftypes.BlockchainEvent) error { res, err := ft.client.R().SetContext(ctx). SetBody(&activatePool{ - RequestID: operationID.String(), + RequestID: opID.String(), PoolID: pool.ProtocolID, - Transaction: tx.Info, + Transaction: event.Info, }). Post("/api/v1/activatepool") if err != nil || !res.IsSuccess() { @@ -365,9 +401,9 @@ func (ft *FFTokens) ActivateTokenPool(ctx context.Context, operationID *fftypes. return nil } -func (ft *FFTokens) MintTokens(ctx context.Context, operationID *fftypes.UUID, poolProtocolID string, mint *fftypes.TokenTransfer) error { +func (ft *FFTokens) MintTokens(ctx context.Context, opID, txID *fftypes.UUID, poolProtocolID string, mint *fftypes.TokenTransfer) error { data, _ := json.Marshal(tokenData{ - TX: mint.TX.ID, + TX: txID, Message: mint.Message, MessageHash: mint.MessageHash, }) @@ -376,7 +412,7 @@ func (ft *FFTokens) MintTokens(ctx context.Context, operationID *fftypes.UUID, p PoolID: poolProtocolID, To: mint.To, Amount: mint.Amount.Int().String(), - RequestID: operationID.String(), + RequestID: opID.String(), Operator: mint.Key, Data: string(data), }). @@ -387,9 +423,9 @@ func (ft *FFTokens) MintTokens(ctx context.Context, operationID *fftypes.UUID, p return nil } -func (ft *FFTokens) BurnTokens(ctx context.Context, operationID *fftypes.UUID, poolProtocolID string, burn *fftypes.TokenTransfer) error { +func (ft *FFTokens) BurnTokens(ctx context.Context, opID, txID *fftypes.UUID, poolProtocolID string, burn *fftypes.TokenTransfer) error { data, _ := json.Marshal(tokenData{ - TX: burn.TX.ID, + TX: txID, Message: burn.Message, MessageHash: burn.MessageHash, }) @@ -399,7 +435,7 @@ func (ft *FFTokens) BurnTokens(ctx context.Context, operationID *fftypes.UUID, p TokenIndex: burn.TokenIndex, From: burn.From, Amount: burn.Amount.Int().String(), - RequestID: operationID.String(), + RequestID: opID.String(), Operator: burn.Key, Data: string(data), }). @@ -410,9 +446,9 @@ func (ft *FFTokens) BurnTokens(ctx context.Context, operationID *fftypes.UUID, p return nil } -func (ft *FFTokens) TransferTokens(ctx context.Context, operationID *fftypes.UUID, poolProtocolID string, transfer *fftypes.TokenTransfer) error { +func (ft *FFTokens) TransferTokens(ctx context.Context, opID, txID *fftypes.UUID, poolProtocolID string, transfer *fftypes.TokenTransfer) error { data, _ := json.Marshal(tokenData{ - TX: transfer.TX.ID, + TX: txID, Message: transfer.Message, MessageHash: transfer.MessageHash, }) @@ -423,7 +459,7 @@ func (ft *FFTokens) TransferTokens(ctx context.Context, operationID *fftypes.UUI From: transfer.From, To: transfer.To, Amount: transfer.Amount.Int().String(), - RequestID: operationID.String(), + RequestID: opID.String(), Operator: transfer.Key, Data: string(data), }). diff --git a/internal/tokens/fftokens/fftokens_test.go b/internal/tokens/fftokens/fftokens_test.go index 4ac4fd784f..58fc24bbbf 100644 --- a/internal/tokens/fftokens/fftokens_test.go +++ b/internal/tokens/fftokens/fftokens_test.go @@ -174,7 +174,7 @@ func TestActivateTokenPool(t *testing.T) { txInfo := map[string]interface{}{ "foo": "bar", } - tx := &fftypes.Transaction{ + ev := &fftypes.BlockchainEvent{ Info: txInfo, } @@ -199,7 +199,7 @@ func TestActivateTokenPool(t *testing.T) { return res, nil }) - err := h.ActivateTokenPool(context.Background(), opID, pool, tx) + err := h.ActivateTokenPool(context.Background(), opID, pool, ev) assert.NoError(t, err) } @@ -214,12 +214,12 @@ func TestActivateTokenPoolError(t *testing.T) { Type: fftypes.TransactionTypeTokenPool, }, } - tx := &fftypes.Transaction{} + ev := &fftypes.BlockchainEvent{} httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/activatepool", httpURL), httpmock.NewJsonResponderOrPanic(500, fftypes.JSONObject{})) - err := h.ActivateTokenPool(context.Background(), fftypes.NewUUID(), pool, tx) + err := h.ActivateTokenPool(context.Background(), fftypes.NewUUID(), pool, ev) assert.Regexp(t, "FF10274", err) } @@ -232,12 +232,9 @@ func TestMintTokens(t *testing.T) { To: "user1", Key: "0x123", Amount: *fftypes.NewFFBigInt(10), - TX: fftypes.TransactionRef{ - ID: fftypes.NewUUID(), - Type: fftypes.TransactionTypeTokenTransfer, - }, } opID := fftypes.NewUUID() + txID := fftypes.NewUUID() httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/mint", httpURL), func(req *http.Request) (*http.Response, error) { @@ -250,7 +247,7 @@ func TestMintTokens(t *testing.T) { "amount": "10", "operator": "0x123", "requestId": opID.String(), - "data": `{"tx":"` + mint.TX.ID.String() + `"}`, + "data": `{"tx":"` + txID.String() + `"}`, }, body) res := &http.Response{ @@ -263,7 +260,7 @@ func TestMintTokens(t *testing.T) { return res, nil }) - err := h.MintTokens(context.Background(), opID, "123", mint) + err := h.MintTokens(context.Background(), opID, txID, "123", mint) assert.NoError(t, err) } @@ -276,7 +273,7 @@ func TestMintTokensError(t *testing.T) { httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/mint", httpURL), httpmock.NewJsonResponderOrPanic(500, fftypes.JSONObject{})) - err := h.MintTokens(context.Background(), fftypes.NewUUID(), "F1", mint) + err := h.MintTokens(context.Background(), fftypes.NewUUID(), fftypes.NewUUID(), "F1", mint) assert.Regexp(t, "FF10274", err) } @@ -290,12 +287,9 @@ func TestBurnTokens(t *testing.T) { From: "user1", Key: "0x123", Amount: *fftypes.NewFFBigInt(10), - TX: fftypes.TransactionRef{ - ID: fftypes.NewUUID(), - Type: fftypes.TransactionTypeTokenTransfer, - }, } opID := fftypes.NewUUID() + txID := fftypes.NewUUID() httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/burn", httpURL), func(req *http.Request) (*http.Response, error) { @@ -309,7 +303,7 @@ func TestBurnTokens(t *testing.T) { "amount": "10", "operator": "0x123", "requestId": opID.String(), - "data": `{"tx":"` + burn.TX.ID.String() + `"}`, + "data": `{"tx":"` + txID.String() + `"}`, }, body) res := &http.Response{ @@ -322,7 +316,7 @@ func TestBurnTokens(t *testing.T) { return res, nil }) - err := h.BurnTokens(context.Background(), opID, "123", burn) + err := h.BurnTokens(context.Background(), opID, txID, "123", burn) assert.NoError(t, err) } @@ -335,7 +329,7 @@ func TestBurnTokensError(t *testing.T) { httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/burn", httpURL), httpmock.NewJsonResponderOrPanic(500, fftypes.JSONObject{})) - err := h.BurnTokens(context.Background(), fftypes.NewUUID(), "F1", burn) + err := h.BurnTokens(context.Background(), fftypes.NewUUID(), fftypes.NewUUID(), "F1", burn) assert.Regexp(t, "FF10274", err) } @@ -350,12 +344,9 @@ func TestTransferTokens(t *testing.T) { To: "user2", Key: "0x123", Amount: *fftypes.NewFFBigInt(10), - TX: fftypes.TransactionRef{ - ID: fftypes.NewUUID(), - Type: fftypes.TransactionTypeTokenTransfer, - }, } opID := fftypes.NewUUID() + txID := fftypes.NewUUID() httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/transfer", httpURL), func(req *http.Request) (*http.Response, error) { @@ -370,7 +361,7 @@ func TestTransferTokens(t *testing.T) { "amount": "10", "operator": "0x123", "requestId": opID.String(), - "data": `{"tx":"` + transfer.TX.ID.String() + `"}`, + "data": `{"tx":"` + txID.String() + `"}`, }, body) res := &http.Response{ @@ -383,7 +374,7 @@ func TestTransferTokens(t *testing.T) { return res, nil }) - err := h.TransferTokens(context.Background(), opID, "123", transfer) + err := h.TransferTokens(context.Background(), opID, txID, "123", transfer) assert.NoError(t, err) } @@ -396,7 +387,7 @@ func TestTransferTokensError(t *testing.T) { httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/transfer", httpURL), httpmock.NewJsonResponderOrPanic(500, fftypes.JSONObject{})) - err := h.TransferTokens(context.Background(), fftypes.NewUUID(), "F1", transfer) + err := h.TransferTokens(context.Background(), fftypes.NewUUID(), fftypes.NewUUID(), "F1", transfer) assert.Regexp(t, "FF10274", err) } @@ -461,8 +452,8 @@ func TestEvents(t *testing.T) { // token-pool: invalid uuid (success) mcb.On("TokenPoolCreated", h, mock.MatchedBy(func(p *tokens.TokenPool) bool { - return p.ProtocolID == "F1" && p.Type == fftypes.TokenTypeFungible && p.Key == "0x0" && p.TransactionID == nil - }), "abc", fftypes.JSONObject{"transactionHash": "abc"}).Return(nil).Once() + return p.ProtocolID == "F1" && p.Type == fftypes.TokenTypeFungible && p.Key == "0x0" && p.TransactionID == nil && p.Event.ProtocolID == "abc" + })).Return(nil).Once() fromServer <- fftypes.JSONObject{ "id": "7", "event": "token-pool", @@ -481,8 +472,8 @@ func TestEvents(t *testing.T) { // token-pool: success mcb.On("TokenPoolCreated", h, mock.MatchedBy(func(p *tokens.TokenPool) bool { - return p.ProtocolID == "F1" && p.Type == fftypes.TokenTypeFungible && p.Key == "0x0" && txID.Equals(p.TransactionID) - }), "abc", fftypes.JSONObject{"transactionHash": "abc"}).Return(nil).Once() + return p.ProtocolID == "F1" && p.Type == fftypes.TokenTypeFungible && p.Key == "0x0" && txID.Equals(p.TransactionID) && p.Event.ProtocolID == "abc" + })).Return(nil).Once() fromServer <- fftypes.JSONObject{ "id": "8", "event": "token-pool", @@ -528,9 +519,9 @@ func TestEvents(t *testing.T) { assert.Equal(t, `{"data":{"id":"10"},"event":"ack"}`, string(msg)) // token-mint: success - mcb.On("TokensTransferred", h, "F1", mock.MatchedBy(func(t *fftypes.TokenTransfer) bool { - return t.Amount.Int().Int64() == 2 && t.To == "0x0" && t.TokenIndex == "" && *t.TX.ID == *txID - }), "abc", fftypes.JSONObject{"transactionHash": "abc"}).Return(nil).Once() + mcb.On("TokensTransferred", h, mock.MatchedBy(func(t *tokens.TokenTransfer) bool { + return t.Amount.Int().Int64() == 2 && t.To == "0x0" && t.TokenIndex == "" && *t.TX.ID == *txID && t.PoolProtocolID == "F1" && t.Event.ProtocolID == "abc" + })).Return(nil).Once() fromServer <- fftypes.JSONObject{ "id": "11", "event": "token-mint", @@ -550,9 +541,9 @@ func TestEvents(t *testing.T) { assert.Equal(t, `{"data":{"id":"11"},"event":"ack"}`, string(msg)) // token-mint: invalid uuid (success) - mcb.On("TokensTransferred", h, "N1", mock.MatchedBy(func(t *fftypes.TokenTransfer) bool { - return t.Amount.Int().Int64() == 1 && t.To == "0x0" && t.TokenIndex == "1" - }), "abc", fftypes.JSONObject{"transactionHash": "abc"}).Return(nil).Once() + mcb.On("TokensTransferred", h, mock.MatchedBy(func(t *tokens.TokenTransfer) bool { + return t.Amount.Int().Int64() == 1 && t.To == "0x0" && t.TokenIndex == "1" && t.PoolProtocolID == "N1" && t.Event.ProtocolID == "abc" + })).Return(nil).Once() fromServer <- fftypes.JSONObject{ "id": "12", "event": "token-mint", @@ -593,9 +584,9 @@ func TestEvents(t *testing.T) { assert.Equal(t, `{"data":{"id":"13"},"event":"ack"}`, string(msg)) // token-transfer: bad message hash (success) - mcb.On("TokensTransferred", h, "F1", mock.MatchedBy(func(t *fftypes.TokenTransfer) bool { - return t.Amount.Int().Int64() == 2 && t.From == "0x0" && t.To == "0x1" && t.TokenIndex == "" - }), "abc", fftypes.JSONObject{"transactionHash": "abc"}).Return(nil).Once() + mcb.On("TokensTransferred", h, mock.MatchedBy(func(t *tokens.TokenTransfer) bool { + return t.Amount.Int().Int64() == 2 && t.From == "0x0" && t.To == "0x1" && t.TokenIndex == "" && t.PoolProtocolID == "F1" && t.Event.ProtocolID == "abc" + })).Return(nil).Once() fromServer <- fftypes.JSONObject{ "id": "14", "event": "token-transfer", @@ -617,9 +608,9 @@ func TestEvents(t *testing.T) { // token-transfer: success messageID := fftypes.NewUUID() - mcb.On("TokensTransferred", h, "F1", mock.MatchedBy(func(t *fftypes.TokenTransfer) bool { - return t.Amount.Int().Int64() == 2 && t.From == "0x0" && t.To == "0x1" && t.TokenIndex == "" && messageID.Equals(t.Message) - }), "abc", fftypes.JSONObject{"transactionHash": "abc"}).Return(nil).Once() + mcb.On("TokensTransferred", h, mock.MatchedBy(func(t *tokens.TokenTransfer) bool { + return t.Amount.Int().Int64() == 2 && t.From == "0x0" && t.To == "0x1" && t.TokenIndex == "" && messageID.Equals(t.Message) && t.PoolProtocolID == "F1" && t.Event.ProtocolID == "abc" + })).Return(nil).Once() fromServer <- fftypes.JSONObject{ "id": "15", "event": "token-transfer", @@ -640,9 +631,9 @@ func TestEvents(t *testing.T) { assert.Equal(t, `{"data":{"id":"15"},"event":"ack"}`, string(msg)) // token-burn: success - mcb.On("TokensTransferred", h, "F1", mock.MatchedBy(func(t *fftypes.TokenTransfer) bool { - return t.Amount.Int().Int64() == 2 && t.From == "0x0" && t.TokenIndex == "0" - }), "abc", fftypes.JSONObject{"transactionHash": "abc"}).Return(nil).Once() + mcb.On("TokensTransferred", h, mock.MatchedBy(func(t *tokens.TokenTransfer) bool { + return t.Amount.Int().Int64() == 2 && t.From == "0x0" && t.TokenIndex == "0" && t.PoolProtocolID == "F1" && t.Event.ProtocolID == "abc" + })).Return(nil).Once() fromServer <- fftypes.JSONObject{ "id": "16", "event": "token-burn", diff --git a/internal/txcommon/txcommon.go b/internal/txcommon/txcommon.go deleted file mode 100644 index be99bb1ebe..0000000000 --- a/internal/txcommon/txcommon.go +++ /dev/null @@ -1,92 +0,0 @@ -// 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 txcommon - -import ( - "context" - - "github.com/hyperledger/firefly/internal/log" - "github.com/hyperledger/firefly/pkg/database" - "github.com/hyperledger/firefly/pkg/fftypes" -) - -type Helper interface { - PersistTransaction(ctx context.Context, tx *fftypes.Transaction) (valid bool, err error) -} - -type transactionHelper struct { - database database.Plugin -} - -func NewTransactionHelper(di database.Plugin) Helper { - return &transactionHelper{ - database: di, - } -} - -func subjectMatch(a *fftypes.TransactionSubject, b *fftypes.TransactionSubject) bool { - return a.Type == b.Type && - a.Signer == b.Signer && - a.Reference != nil && b.Reference != nil && - *a.Reference == *b.Reference && - a.Namespace == b.Namespace -} - -func (t *transactionHelper) PersistTransaction(ctx context.Context, tx *fftypes.Transaction) (valid bool, err error) { - if tx.ID == nil { - log.L(ctx).Errorf("Invalid transaction - ID is nil") - return false, nil // this is not retryable - } - if err := fftypes.ValidateFFNameField(ctx, tx.Subject.Namespace, "namespace"); err != nil { - log.L(ctx).Errorf("Invalid transaction ID='%s' Reference='%s' - invalid namespace '%s': %a", tx.ID, tx.Subject.Reference, tx.Subject.Namespace, err) - return false, nil // this is not retryable - } - existing, err := t.database.GetTransactionByID(ctx, tx.ID) - if err != nil { - return false, err // a persistence failure here is considered retryable (so returned) - } - - switch { - case existing == nil: - // We're the first to write the transaction record on this node - tx.Created = fftypes.Now() - tx.Hash = tx.Subject.Hash() - - case subjectMatch(&tx.Subject, &existing.Subject): - // This is an update to an existing transaction, but the subject is the same - tx.Created = existing.Created - tx.Hash = existing.Hash - - default: - log.L(ctx).Errorf("Invalid transaction ID='%s' Reference='%s' - does not match existing subject", tx.ID, tx.Subject.Reference) - return false, nil // this is not retryable - } - - // Upsert the transaction, ensuring the hash does not change - tx.Status = fftypes.OpStatusSucceeded - err = t.database.UpsertTransaction(ctx, tx, false) - if err != nil { - if err == database.HashMismatch { - log.L(ctx).Errorf("Invalid transaction ID='%s' Reference='%s' - hash mismatch with existing record '%s'", tx.ID, tx.Subject.Reference, tx.Hash) - return false, nil // this is not retryable - } - log.L(ctx).Errorf("Failed to insert transaction ID='%s' Reference='%s': %a", tx.ID, tx.Subject.Reference, err) - return false, err // a persistence failure here is considered retryable (so returned) - } - - return true, nil -} diff --git a/internal/txcommon/txcommon_test.go b/internal/txcommon/txcommon_test.go deleted file mode 100644 index 60a50d9006..0000000000 --- a/internal/txcommon/txcommon_test.go +++ /dev/null @@ -1,200 +0,0 @@ -// 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 txcommon - -import ( - "context" - "fmt" - "testing" - - "github.com/hyperledger/firefly/mocks/databasemocks" - "github.com/hyperledger/firefly/pkg/database" - "github.com/hyperledger/firefly/pkg/fftypes" - "github.com/stretchr/testify/assert" -) - -func TestPersistTransactionNoID(t *testing.T) { - mdb := &databasemocks.Plugin{} - th := NewTransactionHelper(mdb) - - tx := &fftypes.Transaction{} - - valid, err := th.PersistTransaction(context.Background(), tx) - assert.False(t, valid) - assert.NoError(t, err) -} - -func TestPersistTransactionNoNamespace(t *testing.T) { - mdb := &databasemocks.Plugin{} - th := NewTransactionHelper(mdb) - - tx := &fftypes.Transaction{ - ID: fftypes.NewUUID(), - } - - valid, err := th.PersistTransaction(context.Background(), tx) - assert.False(t, valid) - assert.NoError(t, err) -} - -func TestPersistTransactionGetTransactionError(t *testing.T) { - mdb := &databasemocks.Plugin{} - th := NewTransactionHelper(mdb) - - tx := &fftypes.Transaction{ - ID: fftypes.NewUUID(), - Subject: fftypes.TransactionSubject{ - Namespace: "ns1", - }, - } - - mdb.On("GetTransactionByID", context.Background(), tx.ID).Return(nil, fmt.Errorf("pop")) - - valid, err := th.PersistTransaction(context.Background(), tx) - assert.False(t, valid) - assert.EqualError(t, err, "pop") -} - -func TestPersistTransactionSubjectMismatch(t *testing.T) { - mdb := &databasemocks.Plugin{} - th := NewTransactionHelper(mdb) - - tx := &fftypes.Transaction{ - ID: fftypes.NewUUID(), - Subject: fftypes.TransactionSubject{ - Namespace: "ns1", - Reference: fftypes.NewUUID(), - }, - } - existing := &fftypes.Transaction{ - ID: tx.ID, - Subject: fftypes.TransactionSubject{ - Namespace: "ns1", - Reference: fftypes.NewUUID(), - }, - } - - mdb.On("GetTransactionByID", context.Background(), tx.ID).Return(existing, nil) - - valid, err := th.PersistTransaction(context.Background(), tx) - assert.False(t, valid) - assert.NoError(t, err) -} - -func TestPersistTransactionUpsertFail(t *testing.T) { - mdb := &databasemocks.Plugin{} - th := NewTransactionHelper(mdb) - - tx := &fftypes.Transaction{ - ID: fftypes.NewUUID(), - Subject: fftypes.TransactionSubject{ - Namespace: "ns1", - Reference: fftypes.NewUUID(), - }, - } - existing := &fftypes.Transaction{ - ID: tx.ID, - Subject: fftypes.TransactionSubject{ - Namespace: "ns1", - Reference: tx.Subject.Reference, - }, - } - - mdb.On("GetTransactionByID", context.Background(), tx.ID).Return(existing, nil) - mdb.On("UpsertTransaction", context.Background(), tx, false).Return(fmt.Errorf("pop")) - - valid, err := th.PersistTransaction(context.Background(), tx) - assert.False(t, valid) - assert.EqualError(t, err, "pop") -} - -func TestPersistTransactionHashMismatch(t *testing.T) { - mdb := &databasemocks.Plugin{} - th := NewTransactionHelper(mdb) - - tx := &fftypes.Transaction{ - ID: fftypes.NewUUID(), - Subject: fftypes.TransactionSubject{ - Namespace: "ns1", - Reference: fftypes.NewUUID(), - }, - } - existing := &fftypes.Transaction{ - ID: tx.ID, - Subject: fftypes.TransactionSubject{ - Namespace: "ns1", - Reference: tx.Subject.Reference, - }, - } - - mdb.On("GetTransactionByID", context.Background(), tx.ID).Return(existing, nil) - mdb.On("UpsertTransaction", context.Background(), tx, false).Return(database.HashMismatch) - - valid, err := th.PersistTransaction(context.Background(), tx) - assert.False(t, valid) - assert.NoError(t, err) -} - -func TestPersistTransactionUpdateOk(t *testing.T) { - mdb := &databasemocks.Plugin{} - th := NewTransactionHelper(mdb) - - tx := &fftypes.Transaction{ - ID: fftypes.NewUUID(), - Subject: fftypes.TransactionSubject{ - Namespace: "ns1", - Reference: fftypes.NewUUID(), - }, - } - existing := &fftypes.Transaction{ - ID: tx.ID, - Subject: fftypes.TransactionSubject{ - Namespace: "ns1", - Reference: tx.Subject.Reference, - }, - } - - mdb.On("GetTransactionByID", context.Background(), tx.ID).Return(existing, nil) - mdb.On("UpsertTransaction", context.Background(), tx, false).Return(nil) - - valid, err := th.PersistTransaction(context.Background(), tx) - assert.True(t, valid) - assert.NoError(t, err) -} - -func TestPersistTransactionCreateOk(t *testing.T) { - mdb := &databasemocks.Plugin{} - th := NewTransactionHelper(mdb) - - tx := &fftypes.Transaction{ - ID: fftypes.NewUUID(), - Subject: fftypes.TransactionSubject{ - Namespace: "ns1", - Reference: fftypes.NewUUID(), - }, - } - - mdb.On("GetTransactionByID", context.Background(), tx.ID).Return(nil, nil) - mdb.On("UpsertTransaction", context.Background(), tx, false).Return(nil) - - valid, err := th.PersistTransaction(context.Background(), tx) - assert.True(t, valid) - assert.NoError(t, err) - - assert.NotNil(t, tx.Created) - assert.NotNil(t, tx.Hash) -} diff --git a/mocks/assetmocks/manager.go b/mocks/assetmocks/manager.go index 8d12fa2b94..852b0359a5 100644 --- a/mocks/assetmocks/manager.go +++ b/mocks/assetmocks/manager.go @@ -18,13 +18,13 @@ type Manager struct { mock.Mock } -// ActivateTokenPool provides a mock function with given fields: ctx, pool, tx -func (_m *Manager) ActivateTokenPool(ctx context.Context, pool *fftypes.TokenPool, tx *fftypes.Transaction) error { - ret := _m.Called(ctx, pool, tx) +// ActivateTokenPool provides a mock function with given fields: ctx, pool, event +func (_m *Manager) ActivateTokenPool(ctx context.Context, pool *fftypes.TokenPool, event *fftypes.BlockchainEvent) error { + ret := _m.Called(ctx, pool, event) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *fftypes.TokenPool, *fftypes.Transaction) error); ok { - r0 = rf(ctx, pool, tx) + if rf, ok := ret.Get(0).(func(context.Context, *fftypes.TokenPool, *fftypes.BlockchainEvent) error); ok { + r0 = rf(ctx, pool, event) } else { r0 = ret.Error(0) } diff --git a/mocks/blockchainmocks/callbacks.go b/mocks/blockchainmocks/callbacks.go index ad72de1cbe..e44d1ee528 100644 --- a/mocks/blockchainmocks/callbacks.go +++ b/mocks/blockchainmocks/callbacks.go @@ -14,13 +14,13 @@ type Callbacks struct { mock.Mock } -// BatchPinComplete provides a mock function with given fields: batch, signingIdentity, protocolTxID, additionalInfo -func (_m *Callbacks) BatchPinComplete(batch *blockchain.BatchPin, signingIdentity string, protocolTxID string, additionalInfo fftypes.JSONObject) error { - ret := _m.Called(batch, signingIdentity, protocolTxID, additionalInfo) +// BatchPinComplete provides a mock function with given fields: batch, signingIdentity +func (_m *Callbacks) BatchPinComplete(batch *blockchain.BatchPin, signingIdentity string) error { + ret := _m.Called(batch, signingIdentity) var r0 error - if rf, ok := ret.Get(0).(func(*blockchain.BatchPin, string, string, fftypes.JSONObject) error); ok { - r0 = rf(batch, signingIdentity, protocolTxID, additionalInfo) + if rf, ok := ret.Get(0).(func(*blockchain.BatchPin, string) error); ok { + r0 = rf(batch, signingIdentity) } else { r0 = ret.Error(0) } diff --git a/mocks/contractmocks/manager.go b/mocks/contractmocks/manager.go index 07c6bac6dd..4981cdba98 100644 --- a/mocks/contractmocks/manager.go +++ b/mocks/contractmocks/manager.go @@ -156,15 +156,15 @@ func (_m *Manager) GetContractAPIs(ctx context.Context, httpServerURL string, ns } // GetContractEventByID provides a mock function with given fields: ctx, id -func (_m *Manager) GetContractEventByID(ctx context.Context, id *fftypes.UUID) (*fftypes.ContractEvent, error) { +func (_m *Manager) GetContractEventByID(ctx context.Context, id *fftypes.UUID) (*fftypes.BlockchainEvent, error) { ret := _m.Called(ctx, id) - var r0 *fftypes.ContractEvent - if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID) *fftypes.ContractEvent); ok { + var r0 *fftypes.BlockchainEvent + if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID) *fftypes.BlockchainEvent); ok { r0 = rf(ctx, id) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*fftypes.ContractEvent) + r0 = ret.Get(0).(*fftypes.BlockchainEvent) } } @@ -179,15 +179,15 @@ func (_m *Manager) GetContractEventByID(ctx context.Context, id *fftypes.UUID) ( } // GetContractEvents provides a mock function with given fields: ctx, ns, filter -func (_m *Manager) GetContractEvents(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.ContractEvent, *database.FilterResult, error) { +func (_m *Manager) GetContractEvents(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.BlockchainEvent, *database.FilterResult, error) { ret := _m.Called(ctx, ns, filter) - var r0 []*fftypes.ContractEvent - if rf, ok := ret.Get(0).(func(context.Context, string, database.AndFilter) []*fftypes.ContractEvent); ok { + var r0 []*fftypes.BlockchainEvent + if rf, ok := ret.Get(0).(func(context.Context, string, database.AndFilter) []*fftypes.BlockchainEvent); ok { r0 = rf(ctx, ns, filter) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]*fftypes.ContractEvent) + r0 = ret.Get(0).([]*fftypes.BlockchainEvent) } } diff --git a/mocks/databasemocks/plugin.go b/mocks/databasemocks/plugin.go index 25b7598929..92a7fb359d 100644 --- a/mocks/databasemocks/plugin.go +++ b/mocks/databasemocks/plugin.go @@ -271,6 +271,61 @@ func (_m *Plugin) GetBlobs(ctx context.Context, filter database.Filter) ([]*ffty return r0, r1, r2 } +// GetBlockchainEventByID provides a mock function with given fields: ctx, id +func (_m *Plugin) GetBlockchainEventByID(ctx context.Context, id *fftypes.UUID) (*fftypes.BlockchainEvent, error) { + ret := _m.Called(ctx, id) + + var r0 *fftypes.BlockchainEvent + if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID) *fftypes.BlockchainEvent); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*fftypes.BlockchainEvent) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *fftypes.UUID) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetBlockchainEvents provides a mock function with given fields: ctx, filter +func (_m *Plugin) GetBlockchainEvents(ctx context.Context, filter database.Filter) ([]*fftypes.BlockchainEvent, *database.FilterResult, error) { + ret := _m.Called(ctx, filter) + + var r0 []*fftypes.BlockchainEvent + if rf, ok := ret.Get(0).(func(context.Context, database.Filter) []*fftypes.BlockchainEvent); ok { + r0 = rf(ctx, filter) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*fftypes.BlockchainEvent) + } + } + + var r1 *database.FilterResult + if rf, ok := ret.Get(1).(func(context.Context, database.Filter) *database.FilterResult); ok { + r1 = rf(ctx, filter) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*database.FilterResult) + } + } + + var r2 error + if rf, ok := ret.Get(2).(func(context.Context, database.Filter) error); ok { + r2 = rf(ctx, filter) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + // GetChartHistogram provides a mock function with given fields: ctx, ns, intervals, collection func (_m *Plugin) GetChartHistogram(ctx context.Context, ns string, intervals []fftypes.ChartHistogramInterval, collection database.CollectionName) ([]*fftypes.ChartHistogram, error) { ret := _m.Called(ctx, ns, intervals, collection) @@ -427,61 +482,6 @@ func (_m *Plugin) GetContractAPIs(ctx context.Context, ns string, filter databas return r0, r1, r2 } -// GetContractEventByID provides a mock function with given fields: ctx, id -func (_m *Plugin) GetContractEventByID(ctx context.Context, id *fftypes.UUID) (*fftypes.ContractEvent, error) { - ret := _m.Called(ctx, id) - - var r0 *fftypes.ContractEvent - if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID) *fftypes.ContractEvent); ok { - r0 = rf(ctx, id) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*fftypes.ContractEvent) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *fftypes.UUID) error); ok { - r1 = rf(ctx, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetContractEvents provides a mock function with given fields: ctx, filter -func (_m *Plugin) GetContractEvents(ctx context.Context, filter database.Filter) ([]*fftypes.ContractEvent, *database.FilterResult, error) { - ret := _m.Called(ctx, filter) - - var r0 []*fftypes.ContractEvent - if rf, ok := ret.Get(0).(func(context.Context, database.Filter) []*fftypes.ContractEvent); ok { - r0 = rf(ctx, filter) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*fftypes.ContractEvent) - } - } - - var r1 *database.FilterResult - if rf, ok := ret.Get(1).(func(context.Context, database.Filter) *database.FilterResult); ok { - r1 = rf(ctx, filter) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(*database.FilterResult) - } - } - - var r2 error - if rf, ok := ret.Get(2).(func(context.Context, database.Filter) error); ok { - r2 = rf(ctx, filter) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - // GetContractSubscription provides a mock function with given fields: ctx, ns, name func (_m *Plugin) GetContractSubscription(ctx context.Context, ns string, name string) (*fftypes.ContractSubscription, error) { ret := _m.Called(ctx, ns, name) @@ -2129,12 +2129,12 @@ func (_m *Plugin) InsertBlob(ctx context.Context, blob *fftypes.Blob) error { return r0 } -// InsertContractEvent provides a mock function with given fields: ctx, event -func (_m *Plugin) InsertContractEvent(ctx context.Context, event *fftypes.ContractEvent) error { +// InsertBlockchainEvent provides a mock function with given fields: ctx, event +func (_m *Plugin) InsertBlockchainEvent(ctx context.Context, event *fftypes.BlockchainEvent) error { ret := _m.Called(ctx, event) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *fftypes.ContractEvent) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, *fftypes.BlockchainEvent) error); ok { r0 = rf(ctx, event) } else { r0 = ret.Error(0) @@ -2717,13 +2717,13 @@ func (_m *Plugin) UpsertTokenTransfer(ctx context.Context, transfer *fftypes.Tok return r0 } -// UpsertTransaction provides a mock function with given fields: ctx, data, allowHashUpdate -func (_m *Plugin) UpsertTransaction(ctx context.Context, data *fftypes.Transaction, allowHashUpdate bool) error { - ret := _m.Called(ctx, data, allowHashUpdate) +// UpsertTransaction provides a mock function with given fields: ctx, data +func (_m *Plugin) UpsertTransaction(ctx context.Context, data *fftypes.Transaction) error { + ret := _m.Called(ctx, data) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *fftypes.Transaction, bool) error); ok { - r0 = rf(ctx, data, allowHashUpdate) + if rf, ok := ret.Get(0).(func(context.Context, *fftypes.Transaction) error); ok { + r0 = rf(ctx, data) } else { r0 = ret.Error(0) } diff --git a/mocks/eventmocks/event_manager.go b/mocks/eventmocks/event_manager.go index 2263859e05..df3d55a769 100644 --- a/mocks/eventmocks/event_manager.go +++ b/mocks/eventmocks/event_manager.go @@ -51,13 +51,13 @@ func (_m *EventManager) BLOBReceived(dx dataexchange.Plugin, peerID string, hash return r0 } -// BatchPinComplete provides a mock function with given fields: bi, batch, author, protocolTxID, additionalInfo -func (_m *EventManager) BatchPinComplete(bi blockchain.Plugin, batch *blockchain.BatchPin, author string, protocolTxID string, additionalInfo fftypes.JSONObject) error { - ret := _m.Called(bi, batch, author, protocolTxID, additionalInfo) +// BatchPinComplete provides a mock function with given fields: bi, batch, signingIdentity +func (_m *EventManager) BatchPinComplete(bi blockchain.Plugin, batch *blockchain.BatchPin, signingIdentity string) error { + ret := _m.Called(bi, batch, signingIdentity) var r0 error - if rf, ok := ret.Get(0).(func(blockchain.Plugin, *blockchain.BatchPin, string, string, fftypes.JSONObject) error); ok { - r0 = rf(bi, batch, author, protocolTxID, additionalInfo) + if rf, ok := ret.Get(0).(func(blockchain.Plugin, *blockchain.BatchPin, string) error); ok { + r0 = rf(bi, batch, signingIdentity) } else { r0 = ret.Error(0) } @@ -252,13 +252,13 @@ func (_m *EventManager) SubscriptionUpdates() chan<- *fftypes.UUID { return r0 } -// TokenPoolCreated provides a mock function with given fields: ti, pool, protocolTxID, additionalInfo -func (_m *EventManager) TokenPoolCreated(ti tokens.Plugin, pool *tokens.TokenPool, protocolTxID string, additionalInfo fftypes.JSONObject) error { - ret := _m.Called(ti, pool, protocolTxID, additionalInfo) +// TokenPoolCreated provides a mock function with given fields: ti, pool +func (_m *EventManager) TokenPoolCreated(ti tokens.Plugin, pool *tokens.TokenPool) error { + ret := _m.Called(ti, pool) var r0 error - if rf, ok := ret.Get(0).(func(tokens.Plugin, *tokens.TokenPool, string, fftypes.JSONObject) error); ok { - r0 = rf(ti, pool, protocolTxID, additionalInfo) + if rf, ok := ret.Get(0).(func(tokens.Plugin, *tokens.TokenPool) error); ok { + r0 = rf(ti, pool) } else { r0 = ret.Error(0) } @@ -266,13 +266,13 @@ func (_m *EventManager) TokenPoolCreated(ti tokens.Plugin, pool *tokens.TokenPoo return r0 } -// TokensTransferred provides a mock function with given fields: ti, poolProtocolID, transfer, protocolTxID, additionalInfo -func (_m *EventManager) TokensTransferred(ti tokens.Plugin, poolProtocolID string, transfer *fftypes.TokenTransfer, protocolTxID string, additionalInfo fftypes.JSONObject) error { - ret := _m.Called(ti, poolProtocolID, transfer, protocolTxID, additionalInfo) +// TokensTransferred provides a mock function with given fields: ti, transfer +func (_m *EventManager) TokensTransferred(ti tokens.Plugin, transfer *tokens.TokenTransfer) error { + ret := _m.Called(ti, transfer) var r0 error - if rf, ok := ret.Get(0).(func(tokens.Plugin, string, *fftypes.TokenTransfer, string, fftypes.JSONObject) error); ok { - r0 = rf(ti, poolProtocolID, transfer, protocolTxID, additionalInfo) + if rf, ok := ret.Get(0).(func(tokens.Plugin, *tokens.TokenTransfer) error); ok { + r0 = rf(ti, transfer) } else { r0 = ret.Error(0) } diff --git a/mocks/tokenmocks/callbacks.go b/mocks/tokenmocks/callbacks.go index 6df6200403..bdbc2d1942 100644 --- a/mocks/tokenmocks/callbacks.go +++ b/mocks/tokenmocks/callbacks.go @@ -14,13 +14,13 @@ type Callbacks struct { mock.Mock } -// TokenOpUpdate provides a mock function with given fields: plugin, operationID, txState, errorMessage, opOutput -func (_m *Callbacks) TokenOpUpdate(plugin tokens.Plugin, operationID *fftypes.UUID, txState fftypes.OpStatus, errorMessage string, opOutput fftypes.JSONObject) error { - ret := _m.Called(plugin, operationID, txState, errorMessage, opOutput) +// TokenOpUpdate provides a mock function with given fields: plugin, opID, txState, errorMessage, opOutput +func (_m *Callbacks) TokenOpUpdate(plugin tokens.Plugin, opID *fftypes.UUID, txState fftypes.OpStatus, errorMessage string, opOutput fftypes.JSONObject) error { + ret := _m.Called(plugin, opID, txState, errorMessage, opOutput) var r0 error if rf, ok := ret.Get(0).(func(tokens.Plugin, *fftypes.UUID, fftypes.OpStatus, string, fftypes.JSONObject) error); ok { - r0 = rf(plugin, operationID, txState, errorMessage, opOutput) + r0 = rf(plugin, opID, txState, errorMessage, opOutput) } else { r0 = ret.Error(0) } @@ -28,13 +28,13 @@ func (_m *Callbacks) TokenOpUpdate(plugin tokens.Plugin, operationID *fftypes.UU return r0 } -// TokenPoolCreated provides a mock function with given fields: plugin, pool, protocolTxID, additionalInfo -func (_m *Callbacks) TokenPoolCreated(plugin tokens.Plugin, pool *tokens.TokenPool, protocolTxID string, additionalInfo fftypes.JSONObject) error { - ret := _m.Called(plugin, pool, protocolTxID, additionalInfo) +// TokenPoolCreated provides a mock function with given fields: plugin, pool +func (_m *Callbacks) TokenPoolCreated(plugin tokens.Plugin, pool *tokens.TokenPool) error { + ret := _m.Called(plugin, pool) var r0 error - if rf, ok := ret.Get(0).(func(tokens.Plugin, *tokens.TokenPool, string, fftypes.JSONObject) error); ok { - r0 = rf(plugin, pool, protocolTxID, additionalInfo) + if rf, ok := ret.Get(0).(func(tokens.Plugin, *tokens.TokenPool) error); ok { + r0 = rf(plugin, pool) } else { r0 = ret.Error(0) } @@ -42,13 +42,13 @@ func (_m *Callbacks) TokenPoolCreated(plugin tokens.Plugin, pool *tokens.TokenPo return r0 } -// TokensTransferred provides a mock function with given fields: plugin, poolProtocolID, transfer, protocolTxID, additionalInfo -func (_m *Callbacks) TokensTransferred(plugin tokens.Plugin, poolProtocolID string, transfer *fftypes.TokenTransfer, protocolTxID string, additionalInfo fftypes.JSONObject) error { - ret := _m.Called(plugin, poolProtocolID, transfer, protocolTxID, additionalInfo) +// TokensTransferred provides a mock function with given fields: plugin, transfer +func (_m *Callbacks) TokensTransferred(plugin tokens.Plugin, transfer *tokens.TokenTransfer) error { + ret := _m.Called(plugin, transfer) var r0 error - if rf, ok := ret.Get(0).(func(tokens.Plugin, string, *fftypes.TokenTransfer, string, fftypes.JSONObject) error); ok { - r0 = rf(plugin, poolProtocolID, transfer, protocolTxID, additionalInfo) + if rf, ok := ret.Get(0).(func(tokens.Plugin, *tokens.TokenTransfer) error); ok { + r0 = rf(plugin, transfer) } else { r0 = ret.Error(0) } diff --git a/mocks/tokenmocks/plugin.go b/mocks/tokenmocks/plugin.go index bd8d1eb4e6..34b9c8d3a2 100644 --- a/mocks/tokenmocks/plugin.go +++ b/mocks/tokenmocks/plugin.go @@ -19,13 +19,13 @@ type Plugin struct { mock.Mock } -// ActivateTokenPool provides a mock function with given fields: ctx, operationID, pool, tx -func (_m *Plugin) ActivateTokenPool(ctx context.Context, operationID *fftypes.UUID, pool *fftypes.TokenPool, tx *fftypes.Transaction) error { - ret := _m.Called(ctx, operationID, pool, tx) +// ActivateTokenPool provides a mock function with given fields: ctx, opID, pool, event +func (_m *Plugin) ActivateTokenPool(ctx context.Context, opID *fftypes.UUID, pool *fftypes.TokenPool, event *fftypes.BlockchainEvent) error { + ret := _m.Called(ctx, opID, pool, event) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID, *fftypes.TokenPool, *fftypes.Transaction) error); ok { - r0 = rf(ctx, operationID, pool, tx) + if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID, *fftypes.TokenPool, *fftypes.BlockchainEvent) error); ok { + r0 = rf(ctx, opID, pool, event) } else { r0 = ret.Error(0) } @@ -33,13 +33,13 @@ func (_m *Plugin) ActivateTokenPool(ctx context.Context, operationID *fftypes.UU return r0 } -// BurnTokens provides a mock function with given fields: ctx, operationID, poolProtocolID, burn -func (_m *Plugin) BurnTokens(ctx context.Context, operationID *fftypes.UUID, poolProtocolID string, burn *fftypes.TokenTransfer) error { - ret := _m.Called(ctx, operationID, poolProtocolID, burn) +// BurnTokens provides a mock function with given fields: ctx, opID, txID, poolProtocolID, burn +func (_m *Plugin) BurnTokens(ctx context.Context, opID *fftypes.UUID, txID *fftypes.UUID, poolProtocolID string, burn *fftypes.TokenTransfer) error { + ret := _m.Called(ctx, opID, txID, poolProtocolID, burn) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID, string, *fftypes.TokenTransfer) error); ok { - r0 = rf(ctx, operationID, poolProtocolID, burn) + if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID, *fftypes.UUID, string, *fftypes.TokenTransfer) error); ok { + r0 = rf(ctx, opID, txID, poolProtocolID, burn) } else { r0 = ret.Error(0) } @@ -63,13 +63,13 @@ func (_m *Plugin) Capabilities() *tokens.Capabilities { return r0 } -// CreateTokenPool provides a mock function with given fields: ctx, operationID, pool -func (_m *Plugin) CreateTokenPool(ctx context.Context, operationID *fftypes.UUID, pool *fftypes.TokenPool) error { - ret := _m.Called(ctx, operationID, pool) +// CreateTokenPool provides a mock function with given fields: ctx, opID, pool +func (_m *Plugin) CreateTokenPool(ctx context.Context, opID *fftypes.UUID, pool *fftypes.TokenPool) error { + ret := _m.Called(ctx, opID, pool) var r0 error if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID, *fftypes.TokenPool) error); ok { - r0 = rf(ctx, operationID, pool) + r0 = rf(ctx, opID, pool) } else { r0 = ret.Error(0) } @@ -96,13 +96,13 @@ func (_m *Plugin) InitPrefix(prefix config.PrefixArray) { _m.Called(prefix) } -// MintTokens provides a mock function with given fields: ctx, operationID, poolProtocolID, mint -func (_m *Plugin) MintTokens(ctx context.Context, operationID *fftypes.UUID, poolProtocolID string, mint *fftypes.TokenTransfer) error { - ret := _m.Called(ctx, operationID, poolProtocolID, mint) +// MintTokens provides a mock function with given fields: ctx, opID, txID, poolProtocolID, mint +func (_m *Plugin) MintTokens(ctx context.Context, opID *fftypes.UUID, txID *fftypes.UUID, poolProtocolID string, mint *fftypes.TokenTransfer) error { + ret := _m.Called(ctx, opID, txID, poolProtocolID, mint) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID, string, *fftypes.TokenTransfer) error); ok { - r0 = rf(ctx, operationID, poolProtocolID, mint) + if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID, *fftypes.UUID, string, *fftypes.TokenTransfer) error); ok { + r0 = rf(ctx, opID, txID, poolProtocolID, mint) } else { r0 = ret.Error(0) } @@ -138,13 +138,13 @@ func (_m *Plugin) Start() error { return r0 } -// TransferTokens provides a mock function with given fields: ctx, operationID, poolProtocolID, transfer -func (_m *Plugin) TransferTokens(ctx context.Context, operationID *fftypes.UUID, poolProtocolID string, transfer *fftypes.TokenTransfer) error { - ret := _m.Called(ctx, operationID, poolProtocolID, transfer) +// TransferTokens provides a mock function with given fields: ctx, opID, txID, poolProtocolID, transfer +func (_m *Plugin) TransferTokens(ctx context.Context, opID *fftypes.UUID, txID *fftypes.UUID, poolProtocolID string, transfer *fftypes.TokenTransfer) error { + ret := _m.Called(ctx, opID, txID, poolProtocolID, transfer) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID, string, *fftypes.TokenTransfer) error); ok { - r0 = rf(ctx, operationID, poolProtocolID, transfer) + if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID, *fftypes.UUID, string, *fftypes.TokenTransfer) error); ok { + r0 = rf(ctx, opID, txID, poolProtocolID, transfer) } else { r0 = ret.Error(0) } diff --git a/pkg/blockchain/plugin.go b/pkg/blockchain/plugin.go index a4a3f70743..ccad5bfb31 100644 --- a/pkg/blockchain/plugin.go +++ b/pkg/blockchain/plugin.go @@ -81,12 +81,9 @@ type Callbacks interface { // BatchPinComplete notifies on the arrival of a sequenced batch of messages, which might have been // submitted by us, or by any other authorized party in the network. - // Will be combined with he index within the batch, to allocate a sequence to each message in the batch. - // For example a padded block number, followed by a padded transaction index within that block. - // additionalInfo can be used to add opaque protocol specific JSON from the plugin (block numbers etc.) // // Error should will only be returned in shutdown scenarios - BatchPinComplete(batch *BatchPin, signingIdentity string, protocolTxID string, additionalInfo fftypes.JSONObject) error + BatchPinComplete(batch *BatchPin, signingIdentity string) error // ContractEvent notifies on the arrival of any event from a user-created subscription ContractEvent(event *ContractEvent) error @@ -129,7 +126,7 @@ type BatchPin struct { // - The context is a function of: // - A single topic declared in a message - topics are just a string representing a sequence of events that must be processed in order // - A ledger - everone with access to this ledger will see these hashes (Fabric channel, Ethereum chain, EEA privacy group, Corda linear ID) - // - A restricted group - if the mesage is private, these are the nodes that are elible to receive a copy of the private message+data + // - A restricted group - if the mesage is private, these are the nodes that are eligible to receive a copy of the private message+data // - Each message might choose to include multiple topics (and hence attach to multiple contexts) // - This allows multiple contexts to merge - very important in multi-party data matching scenarios // - A batch contains many messages, each with one or more topics @@ -137,15 +134,37 @@ type BatchPin struct { // - For private group communications, the hash is augmented as follow: // - The hashes are salted with a UUID that is only passed off chain (the UUID of the Group). // - The hashes are made unique to the sender - // - The hashes contain a sender specific nonce that is a monotomically increasing number + // - The hashes contain a sender specific nonce that is a monotonically increasing number // for batches sent by that sender, within the context (maintined by the sender FireFly node) Contexts []*fftypes.Bytes32 + + // Event contains info on the underlying blockchain event for this batch pin + Event Event +} + +type Event struct { + // Source indicates where the event originated (ie plugin name) + Source string + + // Name is a short name for the event + Name string + + // ProtocolID is a protocol-specific identifier for the event + ProtocolID string + + // Output is the raw output data from the event + Output fftypes.JSONObject + + // Info is any additional blockchain info for the event (transaction hash, block number, etc) + Info fftypes.JSONObject + + // Timestamp is the time the event was emitted from the blockchain + Timestamp *fftypes.FFTime } type ContractEvent struct { + Event + + // Subscription is the ID assigned to a custom contract subscription by the connector Subscription string - Name string - Outputs fftypes.JSONObject - Info fftypes.JSONObject - Timestamp *fftypes.FFTime } diff --git a/pkg/database/filter_test.go b/pkg/database/filter_test.go index becb64746d..f19e2c4452 100644 --- a/pkg/database/filter_test.go +++ b/pkg/database/filter_test.go @@ -219,15 +219,15 @@ func TestBuildMessageBoolConvert(t *testing.T) { } func TestBuildMessageJSONConvert(t *testing.T) { - fb := TransactionQueryFactory.NewFilter(context.Background()) + fb := OperationQueryFactory.NewFilter(context.Background()) f, err := fb.And( - fb.Eq("info", nil), - fb.Eq("info", `{}`), - fb.Eq("info", []byte(`{}`)), - fb.Eq("info", fftypes.JSONObject{"some": "value"}), + fb.Eq("output", nil), + fb.Eq("output", `{}`), + fb.Eq("output", []byte(`{}`)), + fb.Eq("output", fftypes.JSONObject{"some": "value"}), ).Finalize() assert.NoError(t, err) - assert.Equal(t, `( info == null ) && ( info == '{}' ) && ( info == '{}' ) && ( info == '{"some":"value"}' )`, f.String()) + assert.Equal(t, `( output == null ) && ( output == '{}' ) && ( output == '{}' ) && ( output == '{"some":"value"}' )`, f.String()) } func TestBuildFFNameArrayConvert(t *testing.T) { @@ -324,9 +324,9 @@ func TestQueryFactoryGetBuilder(t *testing.T) { } func TestBuildMessageFailJSONConvert(t *testing.T) { - fb := TransactionQueryFactory.NewFilter(context.Background()) - _, err := fb.Lt("info", map[bool]bool{true: false}).Finalize() - assert.Regexp(t, "FF10149.*info", err) + fb := OperationQueryFactory.NewFilter(context.Background()) + _, err := fb.Lt("output", map[bool]bool{true: false}).Finalize() + assert.Regexp(t, "FF10149.*output", err) } func TestStringsForTypes(t *testing.T) { diff --git a/pkg/database/plugin.go b/pkg/database/plugin.go index e72dcd11e9..b80ad70db8 100644 --- a/pkg/database/plugin.go +++ b/pkg/database/plugin.go @@ -129,8 +129,7 @@ type iBatchCollection interface { type iTransactionCollection interface { // UpsertTransaction - Upsert a transaction - // allowHashUpdate=false throws HashMismatch error if the updated message has a different hash - UpsertTransaction(ctx context.Context, data *fftypes.Transaction, allowHashUpdate bool) (err error) + UpsertTransaction(ctx context.Context, data *fftypes.Transaction) (err error) // UpdateTransaction - Update transaction UpdateTransaction(ctx context.Context, id *fftypes.UUID, update Update) (err error) @@ -448,15 +447,15 @@ type iContractSubscriptionCollection interface { DeleteContractSubscriptionByID(ctx context.Context, id *fftypes.UUID) (err error) } -type iContractEventCollection interface { - // InsertContractEvent - insert an event from an external smart contract - InsertContractEvent(ctx context.Context, event *fftypes.ContractEvent) (err error) +type iBlockchainEventCollection interface { + // InsertBlockchainEvent - insert an event from an external smart contract + InsertBlockchainEvent(ctx context.Context, event *fftypes.BlockchainEvent) (err error) - // GetContractEventByID - get smart contract event by ID - GetContractEventByID(ctx context.Context, id *fftypes.UUID) (*fftypes.ContractEvent, error) + // GetBlockchainEventByID - get smart contract event by ID + GetBlockchainEventByID(ctx context.Context, id *fftypes.UUID) (*fftypes.BlockchainEvent, error) - // GetContractEvents - get smart contract events - GetContractEvents(ctx context.Context, filter Filter) ([]*fftypes.ContractEvent, *FilterResult, error) + // GetBlockchainEvents - get smart contract events + GetBlockchainEvents(ctx context.Context, filter Filter) ([]*fftypes.BlockchainEvent, *FilterResult, error) } // PersistenceInterface are the operations that must be implemented by a database interface plugin. @@ -526,7 +525,7 @@ type PersistenceInterface interface { iFFIEventCollection iContractAPICollection iContractSubscriptionCollection - iContractEventCollection + iBlockchainEventCollection iChartCollection } @@ -540,9 +539,9 @@ type CollectionName string type OrderedUUIDCollectionNS CollectionName const ( - CollectionMessages OrderedUUIDCollectionNS = "messages" - CollectionEvents OrderedUUIDCollectionNS = "events" - CollectionContractEvents OrderedUUIDCollectionNS = "contractevents" + CollectionMessages OrderedUUIDCollectionNS = "messages" + CollectionEvents OrderedUUIDCollectionNS = "events" + CollectionBlockchainEvents OrderedUUIDCollectionNS = "contractevents" ) // OrderedCollection is a collection that is ordered, and that sequence is the only key @@ -689,16 +688,11 @@ var BatchQueryFactory = &queryFields{ // TransactionQueryFactory filter fields for transactions var TransactionQueryFactory = &queryFields{ - "id": &UUIDField{}, - "type": &StringField{}, - "signer": &StringField{}, - "status": &StringField{}, - "reference": &UUIDField{}, - "protocolid": &StringField{}, - "created": &TimeField{}, - "sequence": &Int64Field{}, - "info": &JSONField{}, - "namespace": &StringField{}, + "id": &UUIDField{}, + "type": &StringField{}, + "status": &StringField{}, + "created": &TimeField{}, + "namespace": &StringField{}, } // DataQueryFactory filter fields for data @@ -880,20 +874,21 @@ var TokenBalanceQueryFactory = &queryFields{ // TokenTransferQueryFactory filter fields for token transfers var TokenTransferQueryFactory = &queryFields{ - "localid": &StringField{}, - "pool": &UUIDField{}, - "tokenindex": &StringField{}, - "uri": &StringField{}, - "connector": &StringField{}, - "namespace": &StringField{}, - "key": &StringField{}, - "from": &StringField{}, - "to": &StringField{}, - "amount": &Int64Field{}, - "protocolid": &StringField{}, - "message": &UUIDField{}, - "messagehash": &Bytes32Field{}, - "created": &TimeField{}, + "localid": &StringField{}, + "pool": &UUIDField{}, + "tokenindex": &StringField{}, + "uri": &StringField{}, + "connector": &StringField{}, + "namespace": &StringField{}, + "key": &StringField{}, + "from": &StringField{}, + "to": &StringField{}, + "amount": &Int64Field{}, + "protocolid": &StringField{}, + "message": &UUIDField{}, + "messagehash": &Bytes32Field{}, + "created": &TimeField{}, + "blockchainevent": &UUIDField{}, } // FFIQueryFactory filter fields for contract definitions @@ -933,12 +928,16 @@ var ContractSubscriptionQueryFactory = &queryFields{ "created": &TimeField{}, } -// ContractEventQueryFactory filter fields for contract events -var ContractEventQueryFactory = &queryFields{ +// BlockchainEventQueryFactory filter fields for contract events +var BlockchainEventQueryFactory = &queryFields{ "id": &UUIDField{}, + "source": &StringField{}, "namespace": &StringField{}, - "subscription": &StringField{}, "name": &StringField{}, + "protocolid": &StringField{}, + "subscription": &StringField{}, + "tx.type": &StringField{}, + "tx.id": &UUIDField{}, "timestamp": &TimeField{}, } diff --git a/pkg/fftypes/contractevent.go b/pkg/fftypes/blockchainevent.go similarity index 51% rename from pkg/fftypes/contractevent.go rename to pkg/fftypes/blockchainevent.go index 2d2433185a..5a715a5a87 100644 --- a/pkg/fftypes/contractevent.go +++ b/pkg/fftypes/blockchainevent.go @@ -16,13 +16,16 @@ package fftypes -type ContractEvent struct { - ID *UUID `json:"id,omitempty"` - Sequence int64 `json:"sequence"` - Namespace string `json:"namespace,omitempty"` - Name string `json:"name,omitempty"` - Subscription *UUID `json:"subscription,omitempty"` - Outputs JSONObject `json:"outputs,omitempty"` - Info JSONObject `json:"info,omitempty"` - Timestamp *FFTime `json:"timestamp,omitempty"` +type BlockchainEvent struct { + ID *UUID `json:"id,omitempty"` + Sequence int64 `json:"sequence"` + Source string `json:"source,omitempty"` + Namespace string `json:"namespace,omitempty"` + Name string `json:"name,omitempty"` + Subscription *UUID `json:"subscription,omitempty"` + ProtocolID string `json:"protocolId,omitempty"` + Output JSONObject `json:"output,omitempty"` + Info JSONObject `json:"info,omitempty"` + Timestamp *FFTime `json:"timestamp,omitempty"` + TX TransactionRef `json:"tx"` } diff --git a/pkg/fftypes/event.go b/pkg/fftypes/event.go index 54b6118913..9d15d79efc 100644 --- a/pkg/fftypes/event.go +++ b/pkg/fftypes/event.go @@ -47,8 +47,8 @@ var ( EventTypeContractAPIConfirmed EventType = ffEnum("eventtype", "contract_api_confirmed") // EventTypeContractInterfaceRejected occurs when a new contract API has been rejected EventTypeContractAPIRejected EventType = ffEnum("eventtype", "contract_api_rejected") - // EventTypeContractEvent occurs when a new event has been emitted from a subscribed smart contract - EventTypeContractEvent EventType = ffEnum("eventtype", "contract_event") + // EventTypeBlockchainEvent occurs when a new event has been recorded from the blockchain + EventTypeBlockchainEvent EventType = ffEnum("eventtype", "blockchain_event") ) // Event is an activity in the system, delivered reliably to applications, that indicates something has happened in the network diff --git a/pkg/fftypes/tokenpool.go b/pkg/fftypes/tokenpool.go index 50121f7d6d..c40f348733 100644 --- a/pkg/fftypes/tokenpool.go +++ b/pkg/fftypes/tokenpool.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -58,8 +58,8 @@ type TokenPool struct { } type TokenPoolAnnouncement struct { - Pool *TokenPool `json:"pool"` - TX *Transaction `json:"tx"` + Pool *TokenPool `json:"pool"` + Event *BlockchainEvent `json:"event"` } func (t *TokenPool) Validate(ctx context.Context) (err error) { diff --git a/pkg/fftypes/tokentransfer.go b/pkg/fftypes/tokentransfer.go index fae18e0f5b..493225fa18 100644 --- a/pkg/fftypes/tokentransfer.go +++ b/pkg/fftypes/tokentransfer.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -25,22 +25,22 @@ var ( ) type TokenTransfer struct { - Type TokenTransferType `json:"type" ffenum:"tokentransfertype"` - LocalID *UUID `json:"localId,omitempty"` - Pool *UUID `json:"pool,omitempty"` - TokenIndex string `json:"tokenIndex,omitempty"` - URI string `json:"uri,omitempty"` - Connector string `json:"connector,omitempty"` - Namespace string `json:"namespace,omitempty"` - Key string `json:"key,omitempty"` - From string `json:"from,omitempty"` - To string `json:"to,omitempty"` - Amount FFBigInt `json:"amount"` - ProtocolID string `json:"protocolId,omitempty"` - Message *UUID `json:"message,omitempty"` - MessageHash *Bytes32 `json:"messageHash,omitempty"` - Created *FFTime `json:"created,omitempty"` - TX TransactionRef `json:"tx,omitempty"` + Type TokenTransferType `json:"type" ffenum:"tokentransfertype"` + LocalID *UUID `json:"localId,omitempty"` + Pool *UUID `json:"pool,omitempty"` + TokenIndex string `json:"tokenIndex,omitempty"` + URI string `json:"uri,omitempty"` + Connector string `json:"connector,omitempty"` + Namespace string `json:"namespace,omitempty"` + Key string `json:"key,omitempty"` + From string `json:"from,omitempty"` + To string `json:"to,omitempty"` + Amount FFBigInt `json:"amount"` + ProtocolID string `json:"protocolId,omitempty"` + Message *UUID `json:"message,omitempty"` + MessageHash *Bytes32 `json:"messageHash,omitempty"` + Created *FFTime `json:"created,omitempty"` + BlockchainEvent *UUID `json:"blockchainEvent,omitempty"` } type TokenTransferInput struct { diff --git a/pkg/fftypes/transaction.go b/pkg/fftypes/transaction.go index bde864fc37..7032db192b 100644 --- a/pkg/fftypes/transaction.go +++ b/pkg/fftypes/transaction.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -16,11 +16,6 @@ package fftypes -import ( - "crypto/sha256" - "encoding/json" -) - type TransactionType = FFEnum var ( @@ -40,29 +35,13 @@ type TransactionRef struct { ID *UUID `json:"id,omitempty"` } -// TransactionSubject is the hashable reason for the transaction was performed -type TransactionSubject struct { - Signer string `json:"signer"` // on-chain signing identity - Namespace string `json:"namespace,omitempty"` - Type TransactionType `json:"type" ffenum:"txtype"` - Reference *UUID `json:"reference,omitempty"` -} - -func (t *TransactionSubject) Hash() *Bytes32 { - b, _ := json.Marshal(&t) - var b32 Bytes32 = sha256.Sum256(b) - return &b32 -} - // Transaction represents (blockchain) transactions that were submitted by this // node, with the correlation information to look them up on the underlying // ledger technology type Transaction struct { - ID *UUID `json:"id,omitempty"` - Hash *Bytes32 `json:"hash"` - Subject TransactionSubject `json:"subject"` - Created *FFTime `json:"created"` - Status OpStatus `json:"status"` - ProtocolID string `json:"protocolId,omitempty"` - Info JSONObject `json:"info,omitempty"` + ID *UUID `json:"id,omitempty"` + Namespace string `json:"namespace,omitempty"` + Type TransactionType `json:"type" ffenum:"txtype"` + Created *FFTime `json:"created"` + Status OpStatus `json:"status"` } diff --git a/pkg/fftypes/transaction_test.go b/pkg/fftypes/transaction_test.go deleted file mode 100644 index 488853ca2b..0000000000 --- a/pkg/fftypes/transaction_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// 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 fftypes - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestTransactionHash(t *testing.T) { - batchid := MustParseUUID("39296b6e-91b9-4a61-b279-833c85b04d94") - tx := &Transaction{} - tx.Subject = TransactionSubject{ - Signer: "0x12345", - Namespace: "ns1", - Type: TransactionTypeBatchPin, - Reference: batchid, - } - assert.Equal(t, "0b4ba7041c826cd01a4eaffc22144f219164e21c58b04e7c76bbd58d1f72a1d3", tx.Subject.Hash().String()) -} diff --git a/pkg/tokens/plugin.go b/pkg/tokens/plugin.go index 911b49f3e4..c859ee3273 100644 --- a/pkg/tokens/plugin.go +++ b/pkg/tokens/plugin.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -20,6 +20,7 @@ import ( "context" "github.com/hyperledger/firefly/internal/config" + "github.com/hyperledger/firefly/pkg/blockchain" "github.com/hyperledger/firefly/pkg/fftypes" ) @@ -41,19 +42,19 @@ type Plugin interface { Capabilities() *Capabilities // CreateTokenPool creates a new (fungible or non-fungible) pool of tokens - CreateTokenPool(ctx context.Context, operationID *fftypes.UUID, pool *fftypes.TokenPool) error + CreateTokenPool(ctx context.Context, opID *fftypes.UUID, pool *fftypes.TokenPool) error // ActivateTokenPool activates a pool in order to begin receiving events - ActivateTokenPool(ctx context.Context, operationID *fftypes.UUID, pool *fftypes.TokenPool, tx *fftypes.Transaction) error + ActivateTokenPool(ctx context.Context, opID *fftypes.UUID, pool *fftypes.TokenPool, event *fftypes.BlockchainEvent) error // MintTokens mints new tokens in a pool and adds them to the recipient's account - MintTokens(ctx context.Context, operationID *fftypes.UUID, poolProtocolID string, mint *fftypes.TokenTransfer) error + MintTokens(ctx context.Context, opID, txID *fftypes.UUID, poolProtocolID string, mint *fftypes.TokenTransfer) error // BurnTokens burns tokens from an account - BurnTokens(ctx context.Context, operationID *fftypes.UUID, poolProtocolID string, burn *fftypes.TokenTransfer) error + BurnTokens(ctx context.Context, opID, txID *fftypes.UUID, poolProtocolID string, burn *fftypes.TokenTransfer) error // TransferTokens transfers tokens within a pool from one account to another - TransferTokens(ctx context.Context, operationID *fftypes.UUID, poolProtocolID string, transfer *fftypes.TokenTransfer) error + TransferTokens(ctx context.Context, opID, txID *fftypes.UUID, poolProtocolID string, transfer *fftypes.TokenTransfer) error } // Callbacks is the interface provided to the tokens plugin, to allow it to pass events back to firefly. @@ -75,12 +76,12 @@ type Callbacks interface { // submitted by us, or by any other authorized party in the network. // // Error should will only be returned in shutdown scenarios - TokenPoolCreated(plugin Plugin, pool *TokenPool, protocolTxID string, additionalInfo fftypes.JSONObject) error + TokenPoolCreated(plugin Plugin, pool *TokenPool) error // TokensTransferred notifies on a transfer between token accounts. // // Error should will only be returned in shutdown scenarios - TokensTransferred(plugin Plugin, poolProtocolID string, transfer *fftypes.TokenTransfer, protocolTxID string, additionalInfo fftypes.JSONObject) error + TokensTransferred(plugin Plugin, transfer *TokenTransfer) error } // Capabilities the supported featureset of the tokens @@ -108,4 +109,20 @@ type TokenPool struct { // Standard is the well-defined token standard that this pool conforms to (optional) Standard string + + // Event contains info on the underlying blockchain event for this pool creation + Event blockchain.Event +} + +type TokenTransfer struct { + fftypes.TokenTransfer + + // PoolProtocolID is the ID assigned to the token pool by the connector + PoolProtocolID string + + // Event contains info on the underlying blockchain event for this transfer + Event blockchain.Event + + // TX contains info on the containing Transaction, if this transfer came from FireFly + TX fftypes.TransactionRef } diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 9bda556787..bc5467bdff 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -202,7 +202,7 @@ func beforeE2ETest(t *testing.T) *testState { time.Sleep(3 * time.Second) } - eventNames := "message_confirmed|token_pool_confirmed|token_transfer_confirmed|contract_event" + eventNames := "message_confirmed|token_pool_confirmed|token_transfer_confirmed|blockchain_event" queryString := fmt.Sprintf("namespace=default&ephemeral&autoack&filter.events=%s&changeevents=.*", eventNames) wsUrl1 := url.URL{ @@ -239,17 +239,6 @@ func beforeE2ETest(t *testing.T) *testState { return ts } -func waitForMessageConfirmed(t *testing.T, c chan *fftypes.EventDelivery, msgType fftypes.MessageType) *fftypes.EventDelivery { - for { - ed := <-c - if ed.Type == fftypes.EventTypeMessageConfirmed && ed.Message != nil && ed.Message.Header.Type == msgType { - t.Logf("Detected '%s' event for message '%s' of type '%s'", ed.Type, ed.Message.Header.ID, msgType) - return ed - } - t.Logf("Ignored event '%s'", ed.ID) - } -} - func wsReader(t *testing.T, conn *websocket.Conn) (chan *fftypes.EventDelivery, chan *fftypes.ChangeEvent) { events := make(chan *fftypes.EventDelivery, 100) changeEvents := make(chan *fftypes.ChangeEvent, 100) @@ -285,6 +274,26 @@ func wsReader(t *testing.T, conn *websocket.Conn) (chan *fftypes.EventDelivery, return events, changeEvents } +func waitForEvent(t *testing.T, c chan *fftypes.EventDelivery, eventType fftypes.EventType, ref *fftypes.UUID) { + for { + eventDelivery := <-c + if eventDelivery.Type == eventType && (ref == nil || *ref == *eventDelivery.Reference) { + return + } + } +} + +func waitForMessageConfirmed(t *testing.T, c chan *fftypes.EventDelivery, msgType fftypes.MessageType) *fftypes.EventDelivery { + for { + ed := <-c + if ed.Type == fftypes.EventTypeMessageConfirmed && ed.Message != nil && ed.Message.Header.Type == msgType { + t.Logf("Detected '%s' event for message '%s' of type '%s'", ed.Type, ed.Message.Header.ID, msgType) + return ed + } + t.Logf("Ignored event '%s'", ed.ID) + } +} + func waitForChangeEvent(t *testing.T, client *resty.Client, c chan *fftypes.ChangeEvent, match map[string]interface{}) map[string]interface{} { for { changeEvent := <-c @@ -309,7 +318,7 @@ func waitForChangeEvent(t *testing.T, client *resty.Client, c chan *fftypes.Chan func waitForContractEvent(t *testing.T, client *resty.Client, c chan *fftypes.EventDelivery, match map[string]interface{}) map[string]interface{} { for { eventDelivery := <-c - if eventDelivery.Type == fftypes.EventTypeContractEvent { + if eventDelivery.Type == fftypes.EventTypeBlockchainEvent { event, err := GetContractEvent(t, client, eventDelivery.Event.Reference.String()) if err != nil { t.Logf("WARN: unable to get event: %v", err.Error()) diff --git a/test/e2e/fabric_contract_test.go b/test/e2e/fabric_contract_test.go index 062629ff0b..3868df22d6 100644 --- a/test/e2e/fabric_contract_test.go +++ b/test/e2e/fabric_contract_test.go @@ -129,7 +129,7 @@ func (suite *FabricContractTestSuite) TestE2EContractEvents() { events := GetContractEvents(suite.T(), suite.testState.client1, suite.testState.startTime, sub.ID) assert.Equal(suite.T(), 1, len(events)) assert.Equal(suite.T(), "AssetCreated", events[0].Name) - assert.Equal(suite.T(), asset, events[0].Outputs.GetString("name")) + assert.Equal(suite.T(), asset, events[0].Output.GetString("name")) DeleteContractSubscription(suite.T(), suite.testState.client1, subs[0].ID) subs = GetContractSubscriptions(suite.T(), suite.testState.client1, suite.testState.startTime) diff --git a/test/e2e/restclient.go b/test/e2e/restclient.go index 3296508a02..d8ed8aa368 100644 --- a/test/e2e/restclient.go +++ b/test/e2e/restclient.go @@ -485,7 +485,7 @@ func GetContractSubscriptions(t *testing.T, client *resty.Client, startTime time return subs } -func GetContractEvents(t *testing.T, client *resty.Client, startTime time.Time, subscriptionID *fftypes.UUID) (events []*fftypes.ContractEvent) { +func GetContractEvents(t *testing.T, client *resty.Client, startTime time.Time, subscriptionID *fftypes.UUID) (events []*fftypes.BlockchainEvent) { path := urlContractEvents resp, err := client.R(). SetQueryParam("timestamp", fmt.Sprintf(">%d", startTime.UnixNano())). diff --git a/test/e2e/tokens_test.go b/test/e2e/tokens_test.go index 4c7625d05e..7f40f9d4dd 100644 --- a/test/e2e/tokens_test.go +++ b/test/e2e/tokens_test.go @@ -48,10 +48,10 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { Name: poolName, Type: fftypes.TokenTypeFungible, } - CreateTokenPool(suite.T(), suite.testState.client1, pool, false) + poolResp := CreateTokenPool(suite.T(), suite.testState.client1, pool, false) + poolID := poolResp.ID - <-received1 // event for token pool creation - <-received1 // event for token pool announcement + waitForEvent(suite.T(), received1, fftypes.EventTypePoolConfirmed, poolID) 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) @@ -60,10 +60,7 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { assert.Equal(suite.T(), fftypes.TokenTypeFungible, pools[0].Type) assert.NotEmpty(suite.T(), pools[0].ProtocolID) - poolID := pools[0].ID - - <-received2 // event for token pool creation - <-received2 // event for token pool announcement + waitForEvent(suite.T(), received2, fftypes.EventTypePoolConfirmed, poolID) 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) @@ -76,9 +73,9 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { TokenTransfer: fftypes.TokenTransfer{Amount: *fftypes.NewFFBigInt(1)}, Pool: poolName, } - MintTokens(suite.T(), suite.testState.client1, transfer, false) + transferOut := MintTokens(suite.T(), suite.testState.client1, transfer, false) - <-received1 + waitForEvent(suite.T(), received1, fftypes.EventTypeTransferConfirmed, transferOut.LocalID) transfers := GetTokenTransfers(suite.T(), suite.testState.client1, poolID) assert.Equal(suite.T(), 1, len(transfers)) assert.Equal(suite.T(), "erc1155", transfers[0].Connector) @@ -88,7 +85,7 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { suite.testState.org1.Identity: 1, }) - <-received2 + waitForEvent(suite.T(), received2, fftypes.EventTypeTransferConfirmed, nil) transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolID) assert.Equal(suite.T(), 1, len(transfers)) assert.Equal(suite.T(), "erc1155", transfers[0].Connector) @@ -112,10 +109,9 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { }, }, } - TransferTokens(suite.T(), suite.testState.client1, transfer, false) + transferOut = TransferTokens(suite.T(), suite.testState.client1, transfer, false) - <-received1 // one event for transfer - <-received1 // one event for message + waitForEvent(suite.T(), received1, fftypes.EventTypeMessageConfirmed, transferOut.Message) transfers = GetTokenTransfers(suite.T(), suite.testState.client1, poolID) assert.Equal(suite.T(), 2, len(transfers)) assert.Equal(suite.T(), "erc1155", transfers[0].Connector) @@ -129,8 +125,7 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { suite.testState.org2.Identity: 1, }) - <-received2 // one event for transfer - <-received2 // one event for message + waitForEvent(suite.T(), received2, fftypes.EventTypeMessageConfirmed, transferOut.Message) transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolID) assert.Equal(suite.T(), 2, len(transfers)) assert.Equal(suite.T(), "erc1155", transfers[0].Connector) @@ -145,9 +140,9 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { TokenTransfer: fftypes.TokenTransfer{Amount: *fftypes.NewFFBigInt(1)}, Pool: poolName, } - BurnTokens(suite.T(), suite.testState.client2, transfer, false) + transferOut = BurnTokens(suite.T(), suite.testState.client2, transfer, false) - <-received2 + waitForEvent(suite.T(), received2, fftypes.EventTypeTransferConfirmed, transferOut.LocalID) transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolID) assert.Equal(suite.T(), 3, len(transfers)) assert.Equal(suite.T(), "erc1155", transfers[0].Connector) @@ -159,7 +154,7 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { suite.testState.org2.Identity: 0, }) - <-received1 + waitForEvent(suite.T(), received1, fftypes.EventTypeTransferConfirmed, nil) transfers = GetTokenTransfers(suite.T(), suite.testState.client1, poolID) assert.Equal(suite.T(), 3, len(transfers)) assert.Equal(suite.T(), "erc1155", transfers[0].Connector) @@ -194,10 +189,8 @@ func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() { poolID := poolOut.ID - <-received1 // event for token pool creation - <-received2 // event for token pool announcement - <-received1 // event for token pool creation - <-received2 // event for token pool announcement + waitForEvent(suite.T(), received1, fftypes.EventTypePoolConfirmed, poolID) + waitForEvent(suite.T(), received2, fftypes.EventTypePoolConfirmed, poolID) 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) @@ -217,8 +210,8 @@ func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() { suite.testState.org1.Identity: 1, }) - <-received1 - <-received2 + waitForEvent(suite.T(), received1, fftypes.EventTypeTransferConfirmed, transferOut.LocalID) + waitForEvent(suite.T(), received2, fftypes.EventTypeTransferConfirmed, nil) transfers := GetTokenTransfers(suite.T(), suite.testState.client2, poolID) assert.Equal(suite.T(), 1, len(transfers)) assert.Equal(suite.T(), fftypes.TokenTransferTypeMint, transfers[0].Type) @@ -255,10 +248,8 @@ func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() { 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 + waitForEvent(suite.T(), received1, fftypes.EventTypeMessageConfirmed, transferOut.Message) + waitForEvent(suite.T(), received2, fftypes.EventTypeMessageConfirmed, transferOut.Message) transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolID) assert.Equal(suite.T(), 2, len(transfers)) assert.Equal(suite.T(), fftypes.TokenTransferTypeTransfer, transfers[0].Type) @@ -285,8 +276,8 @@ func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() { suite.testState.org2.Identity: 0, }) - <-received2 - <-received1 + waitForEvent(suite.T(), received2, fftypes.EventTypeTransferConfirmed, transferOut.LocalID) + waitForEvent(suite.T(), received1, fftypes.EventTypeTransferConfirmed, nil) transfers = GetTokenTransfers(suite.T(), suite.testState.client1, poolID) assert.Equal(suite.T(), 3, len(transfers)) assert.Equal(suite.T(), fftypes.TokenTransferTypeBurn, transfers[0].Type)