diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..da730d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +build +coverage +coverage.cov +coverage.html +.idea +new.txt +.vscode +*.pem +avro-tools.jar +.aider* +.env diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..8f19b16 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,112 @@ +# see https://docs.gitlab.com/ee/ci/caching/index.html#cache-go-dependencies + +cache: + paths: + - /apt-cache + - /go/src/github.com + - /go/src/golang.org + - /go/src/google.golang.org + - /go/src/gopkg.in + +stages: + - test + - build + - image + +.golang_based: + tags: + - docker + image: golang:1.24-bookworm + +unit-tests: + extends: + - .golang_based + stage: test + script: + - make test + +# race-detector: +# extends: +# - .golang_based +# stage: test +# script: +# - make race + +# memory-sanitizer: +# extends: +# - .golang_based +# stage: test +# script: +# - make msan + +code-coverage: + extends: + - .golang_based + stage: test + script: + - make cov + +code-coverage-report: + extends: + - .golang_based + stage: test + script: + - make covhtml + rules: + - if: '$CI_COMMIT_BRANCH == "main"' + +# lint_code: +# extends: +# - .golang_based +# stage: test +# script: +# - make lint + +build: + extends: + - .golang_based + stage: build + script: + - make + artifacts: + paths: + - dkafka + +build-image: + tags: + - docker + stage: image + variables: + DOCKER_HOST: "tcp://localhost:2375" + image: docker:git + services: + - docker:19.03.12-dind + script: + - docker build --no-cache --tag "quay.io/ultraio/dkafka:latest" . + cache: + paths: [] + +build-publish-docker: + tags: + - docker + stage: image + variables: + VERSION: "${CI_COMMIT_SHORT_SHA}" + DOCKER_HOST: "tcp://localhost:2375" + image: docker:git + services: + - docker:19.03.12-dind + rules: + - if: $CI_COMMIT_TAG + variables: + VERSION: "${CI_COMMIT_TAG}" + - if: '$CI_COMMIT_BRANCH == "main"' + - if: '$CI_COMMIT_BRANCH == "develop"' + script: + - docker login "quay.io" --username "${QUAY_USERNAME}" --password "${QUAY_PASSWORD}" + - docker build --no-cache --tag "quay.io/ultraio/dkafka:${VERSION}" . + - docker push "quay.io/ultraio/dkafka:${VERSION}" + - docker tag "quay.io/ultraio/dkafka:${VERSION}" "quay.io/ultraio/dkafka:latest" + - docker push "quay.io/ultraio/dkafka:latest" + cache: + paths: [] \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..55240e5 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to Process", + "type": "go", + "request": "attach", + "mode": "local", + "processId": 0 + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1817dbd --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,59 @@ +{ + "cSpell.words": [ + "abicodec", + "avsc", + "bstream", + "cloudevent", + "covhtml", + "DBOP", + "dbops", + "dedupe", + "derr", + "dfuse", + "dgrpc", + "dkafka", + "eewew", + "eoscanada", + "eosio", + "goavro", + "gopkg", + "gotest", + "GRPC", + "jsonpb", + "memoizable", + "mkdir", + "msan", + "pantomath", + "pbabicodec", + "pbbstream", + "pbcodec", + "pbgo", + "pbhealth", + "protobuf", + "ptypes", + "ricardian", + "riferrei", + "setabi", + "specversion", + "srclient", + "SSLCA", + "Structs", + "trxs", + "Undecodable", + "varuint", + "zlog", + "zstd" + ], + "nixEnvSelector.nixFile": "${workspaceFolder}/shell.nix", + "terminal.integrated.profiles.linux": { + "BashWithStartup": { + "path": "bash", + "args": [ + "-c", + "nix-shell" + ] + } + }, + "terminal.integrated.defaultProfile.linux": "BashWithStartup", + "makefile.configureOnOpen": false +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c42abf0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM golang:1.24-bookworm AS build + +WORKDIR /app + +COPY go.mod ./ +COPY go.sum ./ +COPY fork ./fork +COPY action ./action +COPY table ./table +COPY *.go ./ +COPY cmd ./cmd + +RUN go mod download + +RUN go build -o /dkafka -v ./cmd/dkafka + +FROM gcr.io/distroless/base-debian12 + +WORKDIR / + +COPY --from=build /dkafka /dkafka + +USER nonroot:nonroot + +ENTRYPOINT ["/dkafka"] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8771996 --- /dev/null +++ b/Makefile @@ -0,0 +1,252 @@ +PROJECT_NAME := "dkafka" +PKG := "./cmd/$(PROJECT_NAME)" +PKG_LIST := $(shell go list ${PKG}/... | grep -v /vendor/) +GO_FILES := $(shell find . -name '*.go' | grep -v /vendor/ | grep -v _test.go) +BUILD_DIR := "./build" +BINARY_PATH := $(BUILD_DIR)/$(PROJECT_NAME) +COVERAGE_DIR := $(BUILD_DIR) +ENV ?= prod-testnet +KUBECONFIG ?= ~/.kube/dfuse.$(ENV).kube +CODEC ?= "avro" +TOPIC ?= "io.dkafka.test" +CAPTURE ?= true +BATCH_MODE ?=true +SKIP_DBOPS ?= false + +MESSAGE_TYPE ?= '{"create" : "EosioNftFtCreatedNotification","update" : "EosioNftFtUpdatedNotification","issue" : "EosioNftFtIssuedNotification"}[action]' +KEY_EXPRESSION ?= '"action"=="create" ? [data.create.memo] : [transaction_id]' +INCLUDE_EXPRESSION ?= 'executed && (action=="create" || action=="update" || action=="issue") && account=="eosio.nft.ft" && receiver=="eosio.nft.ft"' +# INCLUDE_EXPRESSION ?= 'executed && action=="create" && account=="eosio.nft.ft" && receiver=="eosio.nft.ft"' +# INCLUDE_EXPRESSION ?= 'executed && (action=="create" || action=="issue") && account=="eosio.nft.ft" && receiver=="eosio.nft.ft"' +# ACTIONS_EXPRESSION ?= '{"create":[{"key":"transaction_id", "type":"TestType"}]}' +# ACTIONS_EXPRESSION ?= '{"create":[{"filter": ["factory.a"], "key":"transaction_id", "type":"NftFtCreatedNotification"}]}' +# ACTIONS_EXPRESSION ?= '{"create":[{"filter": ["factory.a"], "key":"string(db_ops[0].new_json.id)", "type":"TestType"}]}' +# ACTIONS_EXPRESSION ?= '{"create":[{"filter": ["insert:factory.a"], "key":"string(db_ops[0].new_json.id)", "type":"NftFtCreatedNotification"}]}' +# ACTIONS_EXPRESSION ?= '{"create":[{"first": "insert:factory.a", "key":"string(db_ops[0].new_json.id)", "type":"NftFtCreatedNotification"}], "issue":[{"filter": "update:factory.a", "split": true, "key":"string(db_ops[0].new_json.id)", "type":"NftFtUpdatedNotification"}]}' + +# stream-act +STREAM_ACT_INCLUDE_EXPRESSION := 'executed && action=="transfer" && account=="eosio.token" && receiver=="eosio.token"' +STREAM_ACT_ACTIONS_EXPRESSION ?= '{"create":[{"key":"transaction_id", "type":"NftFtCreatedNotification"}],"issue":[{"key":"transaction_id", "type":"NftFtIssuedNotification"}]}' +STREAM_ACT_START_BLOCK ?= 49608000 +# CDC +# CDC_TABLES_ACCOUNT ?= 'eosio.nft.ft' +## CDC TABLES +# CDC_TABLES_ACCOUNT ?= 'eosio.token' +# CDC_TABLES_TABLE_NAMES ?= 'accounts:s+k' +CDC_START_BLOCK ?= 135283642 +CDC_ACCOUNT ?= eosio.nft.ft + +CDC_TABLES_START_BLOCK ?= $(CDC_START_BLOCK) +CDC_TABLES_STOP_BLOCK := $(CDC_START_BLOCK) +CDC_TABLES_ACCOUNT ?= $(CDC_ACCOUNT) +CDC_TABLES_TABLE_NAMES ?= *:s+k +## CDC ACTIONS +CDC_ACTIONS_START_BLOCK ?= $(CDC_START_BLOCK) +CDC_ACTIONS_STOP_BLOCK := $(CDC_START_BLOCK) + +CDC_ACTIONS_ACCOUNT ?= $(CDC_ACCOUNT) +CDC_ACTIONS_EXPRESSION ?= {"*":"transaction_id"} +## + +COMPRESSION_TYPE ?= "snappy" +COMPRESSION_LEVEL ?= -1 +MESSAGE_MAX_SIZE ?= 10000000 +# create +# START_BLOCK ?= 37562000 +# issue +START_BLOCK ?= $(CDC_TABLES_START_BLOCK) + +# START_BLOCK ?= 30080000 +STOP_BLOCK ?= 3994800 +# Source: +# https://about.gitlab.com/blog/2017/11/27/go-tools-and-gitlab-how-to-do-continuous-integration-like-a-boss/ +# https://gitlab.com/pantomath-io/demo-tools/-/tree/master + +.PHONY: all dep build clean test cov covhtml lint + +all: build + +lint: ## Lint the files + @golint -set_exit_status ${PKG_LIST} + +test: ## Run unittests + @go test -short + +race: dep ## Run data race detector + @go test -race -short . + +msan: dep ## Run memory sanitizer + @go test -msan -short . + +cov: ## Generate global code coverage report + @mkdir -p $(COVERAGE_DIR) + @go test -covermode=count -coverprofile $(COVERAGE_DIR)/coverage.cov + +covhtml: cov ## Generate global code coverage report in HTML + @mkdir -p $(COVERAGE_DIR) + @go tool cover -html=$(COVERAGE_DIR)/coverage.cov -o $(COVERAGE_DIR)/coverage.html + +dep: ## Get the dependencies + @go get -v -d ./... + @go get -u github.com/golang/lint/golint + +build: ## Build the binary file + @go build -o $(BINARY_PATH) -v $(PKG) + +clean: ## Remove previous build + @rm -rf $(BUILD_DIR) + +bench: ## Run benchmark and save result in new.txt + @go test -bench=adapter -benchmem -run="^$$" -count 7 -cpu 4 | tee new.txt + @benchstat new.txt + +bench-compare: bench ## Compare previous benchmark with new one + @benchstat old.txt new.txt + +bench-save: ## Save last benchmark as the new reference + @mv new.txt old.txt + +up: ## Launch docker compose + @docker-compose up -d + +down: ## Stop docker compose + @docker-compose down + +stream: ## stream expression based localy + $(BINARY_PATH) publish \ + --dfuse-firehose-grpc-addr=localhost:9000 \ + --abicodec-grpc-addr=localhost:9001 \ + --fail-on-undecodable-db-op \ + --kafka-cursor-topic="cursor" \ + --kafka-topic=$(TOPIC) \ + --dfuse-firehose-include-expr=$(INCLUDE_EXPRESSION) \ + --event-keys-expr=$(KEY_EXPRESSION) \ + --event-type-expr=$(MESSAGE_TYPE) \ + --kafka-compression-type=$(COMPRESSION_TYPE) \ + --kafka-compression-level=$(COMPRESSION_LEVEL) \ + --start-block-num=$(START_BLOCK) \ + --kafka-message-max-bytes=$(MESSAGE_MAX_SIZE) + +cdc-tables-mig: ## CDC stream on tables + $(BINARY_PATH) cdc tables \ + --capture=$(CAPTURE) \ + --dfuse-firehose-grpc-addr=localhost:9000 \ + --abicodec-grpc-addr=localhost:9001 \ + --kafka-cursor-topic="cursor" \ + --kafka-topic=$(TOPIC) \ + --kafka-compression-type=$(COMPRESSION_TYPE) \ + --kafka-compression-level=$(COMPRESSION_LEVEL) \ + --kafka-message-max-bytes=$(MESSAGE_MAX_SIZE) \ + --start-block-num=$(CDC_TABLES_START_BLOCK) \ + --codec=$(CODEC) \ + --table-name='$(CDC_TABLES_TABLE_NAMES)' '$(CDC_TABLES_ACCOUNT)' + +cdc-tables: ## CDC stream on tables + $(BINARY_PATH) cdc tables \ + --capture=$(CAPTURE) \ + --dfuse-firehose-grpc-addr=localhost:9000 \ + --abicodec-grpc-addr=localhost:9001 \ + --kafka-topic=$(TOPIC) \ + --kafka-compression-type=$(COMPRESSION_TYPE) \ + --kafka-compression-level=$(COMPRESSION_LEVEL) \ + --kafka-message-max-bytes=$(MESSAGE_MAX_SIZE) \ + --start-block-num=$(CDC_TABLES_START_BLOCK) \ + --stop-block-num=$(CDC_TABLES_STOP_BLOCK) \ + --batch-mode=$(BATCH_MODE) \ + --codec=$(CODEC) \ + --table-name='$(CDC_TABLES_TABLE_NAMES)' '$(CDC_TABLES_ACCOUNT)' + +cdc-actions: ## CDC stream on tables + $(BINARY_PATH) cdc actions \ + --capture=$(CAPTURE) \ + --dfuse-firehose-grpc-addr=localhost:9000 \ + --abicodec-grpc-addr=localhost:9001 \ + --kafka-topic=$(TOPIC) \ + --kafka-compression-type=$(COMPRESSION_TYPE) \ + --kafka-compression-level=$(COMPRESSION_LEVEL) \ + --start-block-num=$(CDC_ACTIONS_START_BLOCK) \ + --stop-block-num=$(CDC_ACTIONS_STOP_BLOCK) \ + --kafka-message-max-bytes=$(MESSAGE_MAX_SIZE) \ + --codec=$(CODEC) \ + --batch-mode=$(BATCH_MODE) \ + --skip-dbops=$(SKIP_DBOPS) \ + --actions-expr='$(CDC_ACTIONS_EXPRESSION)' '$(CDC_ACTIONS_ACCOUNT)' + +cdc-transactions: build up ## CDC stream on tables + $(BINARY_PATH) cdc transactions \ + --capture=$(CAPTURE) \ + --dfuse-firehose-grpc-addr=localhost:9000 \ + --kafka-topic=$(TOPIC) \ + --kafka-compression-type=$(COMPRESSION_TYPE) \ + --kafka-compression-level=$(COMPRESSION_LEVEL) \ + --start-block-num=$(CDC_START_BLOCK) \ + --kafka-message-max-bytes=$(MESSAGE_MAX_SIZE) \ + --codec=$(CODEC) + +stream-act: ## stream actions based localy + $(BINARY_PATH) publish \ + --dfuse-firehose-grpc-addr=localhost:9000 \ + --abicodec-grpc-addr=localhost:9001 \ + --fail-on-undecodable-db-op \ + --kafka-cursor-topic="cursor" \ + --kafka-topic=$(TOPIC) \ + --dfuse-firehose-include-expr=$(STREAM_ACT_INCLUDE_EXPRESSION) \ + --actions-expr=$(STREAM_ACT_ACTIONS_EXPRESSION) \ + --kafka-compression-type=$(COMPRESSION_TYPE) \ + --kafka-compression-level=$(COMPRESSION_LEVEL) \ + --start-block-num=$(STREAM_ACT_START_BLOCK) \ + --kafka-message-max-bytes=$(MESSAGE_MAX_SIZE) + +batch: build up ## run batch localy + $(BINARY_PATH) publish \ + --dfuse-firehose-grpc-addr=localhost:9000 \ + --abicodec-grpc-addr=localhost:9001 \ + --fail-on-undecodable-db-op \ + --batch-mode \ + --kafka-topic=$(TOPIC) \ + --dfuse-firehose-include-expr=$(INCLUDE_EXPRESSION) \ + --event-keys-expr=$(KEY_EXPRESSION) \ + --event-type-expr=$(MESSAGE_TYPE) \ + --kafka-compression-type=$(COMPRESSION_TYPE) \ + --kafka-compression-level=$(COMPRESSION_LEVEL) \ + --start-block-num=$(START_BLOCK) \ + --stop-block-num=$(STOP_BLOCK) \ + --kafka-message-max-bytes=$(MESSAGE_MAX_SIZE) + +schemas: build ## Generate schemas + $(BINARY_PATH) cdc schemas eosio.nft.ft:./testdata/eosio.nft.ft-2.0.abi -o ./build + +# schemas: ## Generate schemas +# @echo ${BINARY_PATH} +# $(BINARY_PATH) cdc schemas eosio.nft.ft:./testdata/eosio.token.abi -o ./build -n io.ultra.test + +forward: ## open port forwarding on dfuse dev + KUBECONFIG=$(KUBECONFIG) kubectl -n ultra-$(ENV) port-forward firehose-v3-0 9000 & + KUBECONFIG=$(KUBECONFIG) kubectl -n ultra-$(ENV) port-forward svc/abicodec-v3 9001:9000 & + +forward-stop: ## stop port fowarding to dfuse + @ps -aux | grep forward | awk '{ print $$2 }' | xargs kill + +help: ## Display this help screen + @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +avro-tools.jar: + wget https://dlcdn.apache.org/avro/avro-1.12.0/java/avro-tools-1.12.0.jar -O avro-tools.jar + sha512sum avro-tools.jar + [ "$(sha512sum avro-tools-1.12.0.jar)" = "$(cat avro-tools-1.12.0.jar.sha512)" ] || \ + { + echo "sha512 doesn't match; removing jar file; please check!" && \ + rm avro-tools.jar + } + + +ABI_ACCOUNT := eosio.nft.ft +ABI_FILE := testdata/eosio.nft.ft-4.0.6-snapshot.abi +TEST_AVRO_PATH := build/test-avro-generation +test-avro-generation: build avro-tools.jar ## test avro generation from abi; check variables to override values like make ABI_FILE=folder/ + mkdir -p $(TEST_AVRO_PATH)/in $(TEST_AVRO_PATH)/out + $(BINARY_PATH) cdc schemas -o $(TEST_AVRO_PATH)/in '$(ABI_ACCOUNT):$(ABI_FILE)' + for file in $$(ls $(TEST_AVRO_PATH)/in); do java -jar avro-tools.jar compile schema "$(TEST_AVRO_PATH)/in/$$file" $(TEST_AVRO_PATH)/out/; done + +docker: ## Build docker image + @docker build --no-cache --tag "quay.io/ultraio/dkafka:latest" . \ No newline at end of file diff --git a/README.md b/README.md index d9559d4..1a03b2f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ kafka cloudevent integration -# Usage +## Usage * Compile with `go install -v ./cmd/dkafka` * Run with: @@ -20,8 +20,30 @@ kafka cloudevent integration --kafka-cursor-topic=_dkafka_cursor \ --kafka-cursor-partition=0 ``` - -# Notes on transaction status and meaning of 'executed' in EOSIO + +## Compression +Some actions may produce a lot of `DBOps` which may lead to reach the limit of kafka max message size. +Or you may just want to reduce the message size. +As the default format of `dkafka` is JSON it can be well compressed. You can use the following options +to play with the compression: +``` + --kafka-compression-level int8 Compression level parameter for algorithm selected by configuration property + kafka-compression-type. Higher values will result in better compression at the + cost of more CPU usage. Usable range is algorithm-dependent: [0-9] for gzip; + [0-12] for lz4; only 0 for snappy; -1 = codec-dependent default compression + level. (default -1) + --kafka-compression-type string Specify the compression type to use for compressing message sets. (none|gzip|snappy|lz4|zstd) (default "none") + --kafka-message-max-bytes int Maximum Kafka protocol request message size. + Due to different framing overhead between protocol versions the producer is + unable to reliably enforce a strict max message limit at produce time, + the message size is checked against your raw uncompressed message. + The broker will also enforce the the topic's max.message.bytes limit + upon receiving your message. So make sure your brokers configuration + match your producers (same apply for consumers) + (see Apache Kafka documentation). (default 1000000) +``` +Using the compression level and type is not enough. The max message size is verified before compression, so you need to increase the `kafka-message-max-bytes` to the max uncompressed message size you'll send (even if after compression your message is 10 times smaller...) +## Notes on transaction status and meaning of 'executed' in EOSIO * Reference: https://github.com/dfuse-io/dkafka/blob/main/pb/eosio-codec/codec.pb.go#L61-L68 * Transaction status can be one of [NONE EXECUTED SOFTFAIL HARDFAIL DELAYED EXPIRED UNKNOWN CANCELED] @@ -29,14 +51,13 @@ kafka cloudevent integration * There is an edge case where a transaction can have a status of SOFTFAIL, but includes an successful call to an error handler (account::action == `eosio::onerror`, handled by the receiver) -- in this case, the the actions of this transaction are actually applied to the chain, and should *probably* be treated the same way as the other *executed* transactoins. * If you want only include all actions that are *executed* in that sense (including the weird SOFTFAIL case), use the field `executed` in your CEL filtering or look for a 'true' value on the `executed` field in your event. -# CEL expression language +## CEL expression language * Reference: https://github.com/google/cel-spec/blob/master/doc/langdef.md * The following flags on dkafka accept CEL expressions to generate a string or an array of strings: * *--event-type-expr* "CEL" --> `string` * *--event-keys-expr* "CEL" --> `[array,of,strings]` - * *--event-extensions-expr* : "key1:CEL1[,key2:CEL2...]" where each CEL expression --> `string` * *--dfuse-firehose-include-expr* "CEL" --> `bool` * the following names are available to be resolved from the EOS blocks, transactions, traces and actions. @@ -45,7 +66,7 @@ kafka cloudevent integration * `action`: name of the action, ex: `transfer` * `block_num`: block number, ex: 234522 * `block_id`: unique ID for the block, ex: `0002332afef44aad7d8f49374398436349372fcdb` - * `block_time`: timestamp of the block + * `block_time`: timestamp of the block in ISO 8601 format * `step`: one of: `NEW`,`UNDO`,`IRREVERSIBLE` * `transaction_id`: unique ID for the transaction, ex: `6d0aae37ab3b81b6783b877f2d54d4708f9f137cc6b23374641be362ff010803` * `transaction_index`: position of that transaction in the block (ex: 5) @@ -53,23 +74,22 @@ kafka cloudevent integration * `execution_index`: position of that action in the transaction, ordered by execution * `data`: map[string]Any corresponding to the params given to the action * `auth`: array of strings corresponding to the authorizations given to the action (who signed it?) - * `input`: bool, if true, the action is top-level (declared in the transaction, not a consequence of another action) - * `notif`: bool, if true, the action is a 'notification' (receiver!=account) - * `executed`: bool, if true, the action was executed successfully (including the error handling of a failed deferred transaction as a SOFTFAIL) - * `scheduled`: bool, if true, the action was scheduled (delayed or deferred) - * `trx_action_count`: number of actions within that transaction - * top5`_trx_actors`: array of the 5 most recurrent actors in a transaction (useful for big transactions with lots of actions) + * `input`: (filter property only) bool, if true, the action is top-level (declared in the transaction, not a consequence of another action) + * `notif`: (filter property only) bool, if true, the action is a 'notification' (receiver!=account) + * `executed`: (filter property only) bool, if true, the action was executed successfully (including the error handling of a failed deferred transaction as a SOFTFAIL) + * `scheduled`: (filter property only) bool, if true, the action was scheduled (delayed or deferred) + * `trx_action_count`: (filter property only) number of actions within that transaction + * `db_ops`: list of database operations executed by this action + * `first_auth_actor`: a shortcut to the first actor name whose has signed the transaction * examples: * to generate two events per action, one with 'account' as the key, one with the 'receiver' as the key (duplicates are removed automatically) `--event-keys-expr="[account,receiver]"` * to set the key to 'updateauth' when the action match, but 'account-{action}' for any other action: `--event-keys-expr="action=='updateauth'?[action] : [account+'-'+action]"` - * to add a header `ce_newaccount` in kafka mesage with the value "yes" it is the action eosio::newaccount "no" ortherwise: - `--event-extensions-expr="ce_newaccount:account+':'+action=='eosio:newaccount'?'yes':'no'"` -# Format of a kafka event PAYLOAD +## Format of a kafka event PAYLOAD * reference: https://github.com/dfuse-io/dkafka/blob/main/app.go#L231-L246 (could change in the near future) @@ -95,6 +115,7 @@ kafka cloudevent integration { "operation": 1, "action_index": 1, + "index": 1, "code": "battlefield1", "scope": "battlefield1", "table_name": "variant", @@ -116,7 +137,7 @@ kafka cloudevent integration } ``` -# Decoding DBOps using ABIs +## Decoding DBOps using ABIs * --local-abi-files flag allows you to specify local JSON files as ABIs for the contracts for which you want to decode DB operations, ex: ``` @@ -145,3 +166,223 @@ dkafka publish \ ``` * --fail-on-undecodable-db-op flag allows you to specify if you want dkafka to fail any time it cannot decode a given dbop to JSON + +## Actions expressions + +In this section I will explain how you can use the `--actions-expr`. +This feature is for advance usage to implement an action-based communication. + +For that, you will use the `--actions-expr` option. +It's a JSON object that describe how each action have to be handled. +This feature allow you to fan-out multiple messages for a give action. +For example, on a NFT issue action you may want to send one message per +update of the state of the NFT factories and one message per newly +created NFTs. +The first level of properties is the name of the actions you filter in +`--dfuse-firehose-include-expr`. Then for each action you specify an array +of (one or many) projections. A projection is an JSON object who defines +an expression for the `key` of the kafka message and the CloudEvents `type` +(ce_type header). Those 2 properties are mandatory. Optionally, you can +specify one of the projection functions on the db_ops: `(filter|first)`. +- first: It's a single message output projection. It's configured with a single + db_op matcher. It traverses the db_ops and stop at the first matching + occurrence and return this only db_ops to build a single message. +- filter: It's a single message output projection. It's configured with a + db_op matcher. It traverses the db_ops and return the matching db_ops. +Additionally you can `split` the resulting db_ops through the `split` property +to send as many message as there is db_ops result. It is useful when an action +insert or update or delete multiple entries in a table and you want to emit +a message per entry like when you issue multiple NFTs in a raw and want a +message per created NTF with the associated to the id of the newly create NTF. + +A Table matcher is defined by an string expression `(:)` where: +- ":" an optional matching property. The operation string value can be + one of the following: (UNKNOWN|INSERT|UPDATE|DELETE) or a numerical positive + value between [0..3] where 0 => UNKNOWN, 1 => INSERT, 2 => UPDATE, 3 => DELETE. + You can use a special character to represent any operation => '*'. It allow + you to write a matcher like this: "*:a.factory" where any operation of the + "a.factory" table will match. +- "": a mandatory name of a given table involved into the action. + It's an exact matcher. + +Warning: this configuration option as a precedence on the `--event-type-expr` and +`--event-keys-expr` options. Therefore if specified then the 2 others are omitted + +Examples: +- simple action matching without db_ops specific projection (identity projection operator). + this is equivalent to the combined usage of --event-type-expr and --event-keys-expr: +``` +{"create":[{"key":"transaction_id", "type":"NftFtCreatedNotification"}]} +``` +- first db_ops projection and db_ops property usage for key definition: +``` +{ + "create":[ + {"first": "1:factory.a", "key":"string(db_ops[0].new_json.id)", "type":"NftFtCreatedNotification"} + ] +} +``` +- multi actions projection: +``` +{ + "create":[ + {"first": "insert:factory.a", "key":"string(db_ops[0].new_json.id)", "type":"NftFtCreatedNotification"} + ], + "issue":[ + {"filter": "update:factory.a", "split": true, "key":"string(db_ops[0].new_json.id)", "type":"NftFtUpdatedNotification"} + ] +} +``` + +## Contributing + +Everything is made around the `Makefile` if you want to use it please install `make` on you system. + +### ARM64 architecture prerequisites (MacOS M chip) + +Confluent libraries are not natively build for ARM64 architectures, therefore, you need to: + +```bash +$ # Install pkg-config +$ brew install pkg-config + +$ # Install OpenSSL +$ brew install openssl + +$ # Make pkg-config aware of OpenSSL installation +$ export PKG_CONFIG_PATH="/opt/homebrew/opt/openssl@3/lib/pkgconfig" + +$ # Run go with "-tags dynamic" to link ARM incompatible libraries dynamically +$ go [go-command] -tags dynamic [parameters] +``` + +### Nix +To setup your dev environment you can install nix and use the `shell.nix` file like +```shell +nix-shell +``` + +Or by using the VSCode extension [Nix env selector](https://marketplace.visualstudio.com/items?itemName=arrterian.nix-env-selector) + +### Build (clean) + +```bash +$ make clean build +``` + +### Test + +```bash +$ make test +``` + +The purpose of this project is to generates Avro schema based on ABI. + +The final test to ensure that you ABI will be 100% compatible you can run +```bash +$ make test-avro-generation +``` +If there is no error then your schema should be valid for a production integration ;) + +#### improvement +* Use testify: https://github.com/stretchr/testify + +### Run +There is several launcher in the `Makefile` and multiple variables can be overridden. +Please have look to the `Makefile` for more details. + +#### Prerequisite +Those commands are required for the streaming of message but not for the schema generation. +Make sure your have at least in the following file: `~/.kube/dfuse.prod-testnet.kube` +This will ensure you can run test against the testnet. + +Start the port forwarding to the firehouse and the abi registry. + +``` +$ make forward +``` +I would suggest to start it in a different terminal + +If for any reasons the connectivity is broken with the firehouse close the port forwarding and re-open it. +``` +$ make forward-stop +$ make forward +``` + +Finally you need to launch the `docker-compose` configuration to enable `kafka`, `kafka-registry` and a topic browser accessible [here](http://localhost:8080) +``` +$ make up +``` +#### Change environment +The `Makefile` defines a `ENV` variable that can be overridden if you want to point to a different environment. +By default it points to the testnet: `prod-testnet`. +``` +$ make forward ENV=dev +``` +#### Run legacy streaming +``` +$ make stream +``` +#### Run CDC on tables +``` +# json mode +$ make cdc-tables +# avro mode +$ make cdc-tables CODEC=avro +``` +#### Run CDC on action +``` +$ make cdc-actions +# json mode +$ make cdc-actions +# avro mode +$ make cdc-tables CODEC=avro +``` + +#### Generate schema +``` +$ make build +$ build/dkafka cdc schemas eosio.nft.ft:./testdata/eosio.nft.ft.abi -o ./build -n io.ultra.test +``` + +## Resources + +- https://developers.eos.io/manuals/eosio.cdt/latest/best-practices/abi/understanding-abi-files + +### goavro + // Supported logical types and their native go types: + // * timestamp-millis - time.Time + // * timestamp-micros - time.Time + // * time-millis - time.Duration + // * time-micros - time.Duration + // * date - int + // * decimal - big.Rat + +## TODO +- [x] benchmark avro codec +- [x] implement a cache on a top of KafkaAvroABICodec +- [x] implement correlation id +- [x] provide a selector for table key => --table-name factory.a:s+k | factory.a:s | factory.a:k |factory.a +- [ ] add ABI.nativeTime bool to skip time to string conversion in abidecoder `read` method as it is done for `ABI.fitNodeos` property with logical type +- [x] fix the issue with some types mapping in `schema.go` like `asset` +- [ ] add support of variants https://developers.eos.io/manuals/eosio.cdt/latest/tutorials/abi-variants +- [x] add `ce_dataschema` header +- [x] set `ce_datacontenttype` header at codec level or as a return type +- [x] user ce_type instead of source for the `ce_id` +- [x] schema registry integration +- [x] avro codec +- [x] avro schema generation +- [x] parametrize the record name with the ce_type +- [x] add meta info in avro schema `meta: {compatibility: FORWARD, type: notification version: 1.2.1} +- [x] add support for cdc on tables +- [x] add support for cdc on actions +- [x] see if we need a defaulting on the key expression for cdc tables like: + `key := fmt.Sprintf("%s:%s", decodedDBOp.Scope, decodedDBOp.PrimaryKey)` +- [x] see with blockchain team for the default type mapping +- [x] validate with blockchain team cdc table message key +- [ ] patch (pool request) on goavro to support uint32 and unit64... overflow => negative value +- [ ] pool request on eos-go for native decode functions +- [ ] add option to exclude certain actions when in cdc table mode +- [x] add the cursor details in `DkafkaCheckpoint` message in avro and json format depending of the codec. +- [ ] optimize the bootstrap time to avoid to read all the blocks from 0. +- [ ] make action and table schema consistent by using the same deserialization mechanism with the action trace row_data \ No newline at end of file diff --git a/abidecoder.go b/abidecoder.go index a37a8bb..97fa76a 100644 --- a/abidecoder.go +++ b/abidecoder.go @@ -2,34 +2,109 @@ package dkafka import ( "context" + "encoding/hex" "encoding/json" "fmt" + "math" "os" + "strconv" + "strings" pbabicodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/abicodec/v1" pbcodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/codec/v1" "github.com/eoscanada/eos-go" + pbbstream "github.com/streamingfast/pbgo/dfuse/bstream/v1" + "go.uber.org/zap" ) +type ABI struct { + *eos.ABI + AbiBlockNum uint32 + Account string + Irreversible bool +} + +type CodecId struct { + Account string + Name string +} + +type ABICodec interface { + IsNOOP() bool + DecodeDBOp(in *pbcodec.DBOp, blockNum uint32) (*decodedDBOp, error) + GetCodec(codecId CodecId, blockNum uint32) (Codec, error) + UpdateABI(blockNum uint32, step pbbstream.ForkStep, trxID string, actionTrace *pbcodec.ActionTrace) error +} + +type JsonABICodec struct { + *ABIDecoder + codec Codec + account string +} + +func (c *JsonABICodec) GetCodec(codecId CodecId, blockNum uint32) (Codec, error) { + return c.codec, nil +} + +func (c *JsonABICodec) UpdateABI(_ uint32, _ pbbstream.ForkStep, _ string, _ *pbcodec.ActionTrace) error { + return nil +} + +func NewJsonABICodec( + decoder *ABIDecoder, + account string, +) ABICodec { + return &JsonABICodec{ + decoder, + NewJSONCodec(), + account, + } +} + +// MessageSchemaSupplier is a function that return the specific message schema +// of a given entity (i.e. table or action) +type MessageSchemaSupplier = func(string, *ABI) (MessageSchema, error) + +// ABIDecoder legacy abi codec does not support schema registry type ABIDecoder struct { - overrides map[string]*eos.ABI + overrides map[string]*ABI abiCodecCli pbabicodec.DecoderClient - abisCache map[string]*abiItem + abisCache map[string]*ABI + context context.Context } func (a *ABIDecoder) IsNOOP() bool { return a.overrides == nil && a.abiCodecCli == nil } -// LoadABIFiles will load ABIs for different accounts from JSON files -func LoadABIFiles(abiFiles map[string]string) (map[string]*eos.ABI, error) { - out := make(map[string]*eos.ABI) - for contract, abiFile := range abiFiles { - f, err := os.Open(abiFile) +func ParseABIFileSpecs(specs []string) (abiFileSpecs map[string]string, err error) { + abiFileSpecs = make(map[string]string) + for _, ext := range specs { + account, abiPath, err := ParseABIFileSpec(ext) if err != nil { - return nil, fmt.Errorf("opening abi file %s: %w", abiFile, err) + break } - abi, err := eos.NewABI(f) + abiFileSpecs[account] = abiPath + } + return +} + +func ParseABIFileSpec(spec string) (account string, abiPath string, err error) { + kv := strings.SplitN(spec, ":", 2) + if len(kv) != 2 { + err = fmt.Errorf("invalid value for local ABI file: %s", spec) + } else { + account = kv[0] + abiPath = kv[1] + } + return +} + +// LoadABIFiles will load ABIs for different accounts from JSON files +func LoadABIFiles(abiFiles map[string]string) (map[string]*ABI, error) { + out := make(map[string]*ABI) + for contract, abiFile := range abiFiles { + abi, err := LoadABIFile(contract, abiFile) if err != nil { return nil, fmt.Errorf("reading abi file %s: %w", abiFile, err) } @@ -38,24 +113,74 @@ func LoadABIFiles(abiFiles map[string]string) (map[string]*eos.ABI, error) { return out, nil } +func LoadABIFile(account string, abiFile string) (*ABI, error) { + kv := strings.SplitN(abiFile, ":", 2) //[abiFilePath] - [abiFilePath, abiNumber] + var abiPath = abiFile + var abiBlockNum = uint64(0) + if len(kv) == 2 { + abiPath = kv[0] + var err error + abiBlockNum, err = strconv.ParseUint(kv[1], 10, 32) + if err != nil { + return nil, err + } + } + f, err := os.Open(abiPath) + if err == nil { + defer f.Close() + eosAbi, err := eos.NewABI(f) + abi := &ABI{ + ABI: eosAbi, + AbiBlockNum: uint32(abiBlockNum), + Account: account, + Irreversible: true, + } + return abi, err + } + return nil, err +} + func NewABIDecoder( - overrides map[string]*eos.ABI, + overrides map[string]*ABI, abiCodecCli pbabicodec.DecoderClient, + context context.Context, ) *ABIDecoder { return &ABIDecoder{ overrides: overrides, abiCodecCli: abiCodecCli, - abisCache: make(map[string]*abiItem), + abisCache: make(map[string]*ABI), + context: context, } } type decodedDBOp struct { *pbcodec.DBOp - NewJSON *json.RawMessage `json:"new_json,omitempty"` - OldJSON *json.RawMessage `json:"old_json,omitempty"` + NewJSON map[string]interface{} `json:"new_json,omitempty"` + OldJSON map[string]interface{} `json:"old_json,omitempty"` } -func (a *ABIDecoder) abi(contract string, blockNum uint32, forceRefresh bool) (*eos.ABI, error) { +func (dbOp *decodedDBOp) asMap(dbOpRecordName string, dbOpIndex int) map[string]interface{} { + asMap := newDBOpBasic(dbOp.DBOp, dbOpIndex) + addOptional(&asMap, "old_json", dbOp.OldJSON) + addOptional(&asMap, "new_json", dbOp.NewJSON) + // addOptionalRecord(&asMap, "old_json", dbOpRecordName, dbOp.OldJSON) + // addOptionalRecord(&asMap, "new_json", dbOpRecordName, dbOp.NewJSON) + return asMap +} + +func addOptional(m *map[string]interface{}, key string, value map[string]interface{}) { + if len(value) > 0 { + (*m)[key] = value + } +} + +// func addOptionalRecord(m *map[string]interface{}, key string, rType string, value map[string]interface{}) { +// if len(value) > 0 { +// addOptional(m, key, map[string]interface{}{rType: value}) +// } +// } + +func (a *ABIDecoder) abi(contract string, blockNum uint32, forceRefresh bool) (*ABI, error) { if a.overrides != nil { if abi, ok := a.overrides[contract]; ok { return abi, nil @@ -68,13 +193,13 @@ func (a *ABIDecoder) abi(contract string, blockNum uint32, forceRefresh bool) (* if !forceRefresh { if abiObj, ok := a.abisCache[contract]; ok { - if abiObj.blockNum < blockNum { - return abiObj.abi, nil + if abiObj.AbiBlockNum < blockNum { + return abiObj, nil } } } - - resp, err := a.abiCodecCli.GetAbi(context.Background(), &pbabicodec.GetAbiRequest{ + zlog.Info("ABIDecoder.abi(...) => call onReload()", zap.String("contract", contract), zap.Uint32("block_num", blockNum), zap.Bool("force_refresh", forceRefresh)) + resp, err := a.abiCodecCli.GetAbi(a.context, &pbabicodec.GetAbiRequest{ Account: contract, AtBlockNum: blockNum, }) @@ -82,18 +207,16 @@ func (a *ABIDecoder) abi(contract string, blockNum uint32, forceRefresh bool) (* return nil, fmt.Errorf("unable to get abi for contract %q: %w", contract, err) } - var abi *eos.ABI - err = json.Unmarshal([]byte(resp.JsonPayload), &abi) + var eosAbi *eos.ABI + err = json.Unmarshal([]byte(resp.JsonPayload), &eosAbi) if err != nil { return nil, fmt.Errorf("unable to decode abi for contract %q: %w", contract, err) } - + var abi = ABI{eosAbi, resp.AbiBlockNum, contract, true} + zlog.Info("new ABI loaded", zap.String("contract", contract), zap.Uint32("block_num", blockNum), zap.Uint32("abi_block_num", abi.AbiBlockNum)) // store abi in cache for late uses - a.abisCache[contract] = &abiItem{ - abi: abi, - blockNum: resp.AbiBlockNum, - } - return abi, nil + a.abisCache[contract] = &abi + return &abi, nil } func (a *ABIDecoder) decodeDBOp(op *decodedDBOp, blockNum uint32, forceRefresh bool) error { @@ -107,24 +230,31 @@ func (a *ABIDecoder) decodeDBOp(op *decodedDBOp, blockNum uint32, forceRefresh b } if len(op.NewData) > 0 { - bytes, err := abi.DecodeTableRowTyped(tableDef.Type, op.NewData) + asMap, err := abi.DecodeTableRowTypedNative(tableDef.Type, op.NewData) if err != nil { return fmt.Errorf("decode row: %w", err) } - asJSON := json.RawMessage(bytes) - op.NewJSON = &asJSON + op.NewJSON = asMap } if len(op.OldData) > 0 { - bytes, err := abi.DecodeTableRowTyped(tableDef.Type, op.OldData) + asMap, err := abi.DecodeTableRowTypedNative(tableDef.Type, op.OldData) if err != nil { return fmt.Errorf("decode row: %w", err) } - asJSON := json.RawMessage(bytes) - op.OldJSON = &asJSON + op.OldJSON = asMap } return nil } +func (a *ABIDecoder) DecodeDBOp(in *pbcodec.DBOp, blockNum uint32) (decoded *decodedDBOp, err error) { + decoded = &decodedDBOp{DBOp: in} + err = a.decodeDBOp(decoded, blockNum, false) + if err != nil { + err = a.decodeDBOp(decoded, blockNum, true) //force refreshing ABI from cache + } + return +} + func (a *ABIDecoder) DecodeDBOps(in []*pbcodec.DBOp, blockNum uint32) (decodedDBOps []*decodedDBOp, err error) { for _, op := range in { decoded := &decodedDBOp{DBOp: op} @@ -156,7 +286,29 @@ func (a *ABIDecoder) DecodeDBOps(in []*pbcodec.DBOp, blockNum uint32) (decodedDB return } -type abiItem struct { - abi *eos.ABI - blockNum uint32 +func DecodeABI(trxID string, account string, hexData string) (abi *eos.ABI, err error) { + if hexData == "" { + zlog.Warn("empty ABI in 'setabi' action", zap.String("account", account), zap.String("transaction_id", trxID)) + return nil, fmt.Errorf("empty ABI in 'setabi' action, account: %s, trx: %s", account, trxID) + } + abiData, err := hex.DecodeString(hexData) + if err != nil { + zlog.Error("failed to hex decode abi string", zap.String("account", account), zap.String("transaction_id", trxID), zap.Error(err)) + return nil, fmt.Errorf("failed to hex decode abi string, account: %s, trx: %s, error: %w", account, trxID, err) + } + err = eos.UnmarshalBinary(abiData, &abi) + if err != nil { + abiHexCutAt := math.Min(50, float64(len(hexData))) + + zlog.Error("failed to unmarshal abi from binary", + zap.String("account", account), + zap.String("transaction_id", trxID), + zap.String("abi_hex_prefix", hexData[0:int(abiHexCutAt)]), + zap.Error(err), + ) + + return nil, fmt.Errorf("failed to unmarshal abi from binary, account: %s, trx: %s, error: %w", account, trxID, err) + } + zlog.Debug("setting new abi", zap.String("account", account), zap.String("transaction_id", trxID)) + return } diff --git a/abidecoder_test.go b/abidecoder_test.go new file mode 100644 index 0000000..d11a88f --- /dev/null +++ b/abidecoder_test.go @@ -0,0 +1,158 @@ +package dkafka + +import ( + "encoding/json" + "testing" + + "github.com/eoscanada/eos-go" +) + +func TestParseABIFileSpec(t *testing.T) { + type args struct { + spec string + } + tests := []struct { + name string + args args + wantAccount string + wantAbiPath string + wantErr bool + }{ + { + name: "default", + args: args{ + spec: "eosio.nft.ft:testdata/eosio.nft.ft.abi", + }, + wantAccount: "eosio.nft.ft", + wantAbiPath: "testdata/eosio.nft.ft.abi", + wantErr: false, + }, + { + name: "with-block-number", + args: args{ + spec: "eosio.nft.ft:testdata/eosio.nft.ft.abi:1", + }, + wantAccount: "eosio.nft.ft", + wantAbiPath: "testdata/eosio.nft.ft.abi:1", + wantErr: false, + }, + { + name: "invalid", + args: args{ + spec: "eosio.nft.f", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotAccount, gotAbiPath, err := ParseABIFileSpec(tt.args.spec) + if (err != nil) != tt.wantErr { + t.Errorf("ParseABIFileSpec() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotAccount != tt.wantAccount { + t.Errorf("ParseABIFileSpec() gotAccount = %v, want %v", gotAccount, tt.wantAccount) + } + if gotAbiPath != tt.wantAbiPath { + t.Errorf("ParseABIFileSpec() gotAbiPath = %v, want %v", gotAbiPath, tt.wantAbiPath) + } + }) + } +} + +func TestLoadABIFile(t *testing.T) { + type args struct { + account string + abiFile string + } + tests := []struct { + name string + args args + wantABIBlockNum uint32 + wantErr bool + }{ + { + name: "default", + args: args{ + account: "eosio.nft.ft", + abiFile: "testdata/eosio.nft.ft.abi", + }, + wantABIBlockNum: uint32(0), + wantErr: false, + }, + { + name: "with-block-number", + args: args{ + account: "eosio.nft.ft", + abiFile: "testdata/eosio.nft.ft.abi:1", + }, + wantABIBlockNum: uint32(1), + wantErr: false, + }, + { + name: "invalid", + args: args{ + abiFile: "eosio.nft.f", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := LoadABIFile(tt.args.account, tt.args.abiFile) + if (err != nil) != tt.wantErr { + t.Errorf("LoadABIFile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != nil && got.AbiBlockNum != tt.wantABIBlockNum { + t.Errorf("LoadABIFile() got AbiBlockNum = %v, want %v", got, tt.wantABIBlockNum) + } + }) + } +} + +func TestDecodeABI(t *testing.T) { + type args struct { + trxID string + account string + hexDataPath string + } + tests := []struct { + name string + args args + wantAbi string + wantErr bool + }{ + { + name: "eosio.nft.ft", + args: args{ + trxID: "test", + account: "test", + hexDataPath: "testdata/abi.hex", + }, + wantAbi: "testdata/abi.json", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hexData := string(readFileFromTestdata(t, tt.args.hexDataPath)) + abiJson := readFileFromTestdata(t, tt.wantAbi) + expectedAbi := &eos.ABI{} + json.Unmarshal(abiJson, expectedAbi) + gotAbi, err := DecodeABI(tt.args.trxID, tt.args.account, hexData) + if (err != nil) != tt.wantErr { + t.Errorf("DecodeABI() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if gotAbi == nil { + t.Errorf("DecodeABI() = %v, want %v", gotAbi, expectedAbi) + } + + // if !reflect.DeepEqual(gotAbi, expectedAbi) { + // t.Errorf("DecodeABI() = %v, want %v", gotAbi, expectedAbi) + // } + }) + } +} diff --git a/action/filter.go b/action/filter.go new file mode 100644 index 0000000..ac5dfc8 --- /dev/null +++ b/action/filter.go @@ -0,0 +1,7 @@ +package action + +import "fmt" + +func Filter(account string) string { + return fmt.Sprintf("(account==\"%s\" && receiver==\"%s\")", account, account) +} diff --git a/action/filter_test.go b/action/filter_test.go new file mode 100644 index 0000000..fa0d5d9 --- /dev/null +++ b/action/filter_test.go @@ -0,0 +1,36 @@ +package action + +import "testing" + +func TestFilter(t *testing.T) { + type args struct { + account string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "eosio.token", + args: args{ + account: "eosio.token", + }, + want: `(account=="eosio.token" && receiver=="eosio.token")`, + }, + { + name: "eosio.eba", + args: args{ + account: "eosio.eba", + }, + want: `(account=="eosio.eba" && receiver=="eosio.eba")`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Filter(tt.args.account); got != tt.want { + t.Errorf("Filter() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/adapt.go b/adapt.go new file mode 100644 index 0000000..ddbfc05 --- /dev/null +++ b/adapt.go @@ -0,0 +1,102 @@ +package dkafka + +import ( + "time" + + "github.com/confluentinc/confluent-kafka-go/kafka" + pbcodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/codec/v1" + pbbstream "github.com/streamingfast/pbgo/dfuse/bstream/v1" + "go.uber.org/zap" +) + +type Adapter interface { + Adapt(blkStep BlockStep) ([]*kafka.Message, error) +} + +type BlockStep struct { + blk *pbcodec.Block + step pbbstream.ForkStep + cursor string + previousCursor string +} + +func (bs BlockStep) time() time.Time { + return bs.blk.MustTime().UTC() +} + +func (bs BlockStep) timeString() string { + blkTime := bs.time() + return blkTime.Format(time.RFC3339) +} + +func (bs BlockStep) timeHeader() kafka.Header { + return kafka.Header{ + Key: "ce_time", + Value: []byte(bs.timeString()), + } +} + +func (bs BlockStep) opaqueCursor() string { + return bs.cursor +} + +func (bs BlockStep) previousOpaqueCursor() string { + return bs.previousCursor +} +func (bs BlockStep) blockId() string { + return bs.blk.Id +} +func (bs BlockStep) blockNum() uint32 { + return bs.blk.Number +} + +type CdCAdapter struct { + topic string + saveBlock SaveBlock + generator GeneratorAtTransactionLevel + headers []kafka.Header + abiCodec ABICodec +} + +// orderSliceOnBlockStep reverse the slice order is the block step is UNDO +func orderSliceOnBlockStep[T any](input []T, step pbbstream.ForkStep) []T { + output := input + if step == pbbstream.ForkStep_STEP_UNDO { + output = Reverse(input) + } + return output +} + +func (m *CdCAdapter) Adapt(blkStep BlockStep) ([]*kafka.Message, error) { + blk := blkStep.blk + step := sanitizeStep(blkStep.step.String()) + + m.saveBlock(blk) + if blk.Number%1000 == 0 { + zlog.Info("incoming block 1/1000", zap.Uint32("block_num", blk.Number), zap.String("step", step), zap.Int("length_filtered_trx_traces", len(blk.FilteredTransactionTraces))) + } + if blk.Number%10 == 0 { + zlog.Debug("incoming block 1/10", zap.Uint32("block_num", blk.Number), zap.String("step", step), zap.Int("length_filtered_trx_traces", len(blk.FilteredTransactionTraces))) + } + msgs := make([]*kafka.Message, 0) + trxs := blk.TransactionTraces() + zlog.Debug("adapt block", zap.Uint32("num", blk.Number), zap.Int("nb_trx", len(trxs))) + for _, trx := range orderSliceOnBlockStep(trxs, blkStep.step) { + transactionTracesReceived.Inc() + trxCtx := TransactionContext{ + block: blk, + stepName: step, + transaction: trx, + cursor: blkStep.cursor, + step: blkStep.step, + blockStep: blkStep, + } + msgs1, err := m.generator.Apply(trxCtx) + if err != nil { + return nil, err + } + msgs = append(msgs, msgs1...) + } + zlog.Debug("produced kafka messages", zap.Uint32("block_num", blk.Number), zap.String("step", step), zap.Int("nb_messages", len(msgs))) + return msgs, nil +} diff --git a/adapt_test.go b/adapt_test.go new file mode 100644 index 0000000..3623ee5 --- /dev/null +++ b/adapt_test.go @@ -0,0 +1,393 @@ +package dkafka + +import ( + "context" + "encoding/json" + "fmt" + "path" + "strings" + "testing" + + "github.com/confluentinc/confluent-kafka-go/kafka" + pbcodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/codec/v1" + "github.com/eoscanada/eos-go" + "github.com/golang/protobuf/jsonpb" + "github.com/riferrei/srclient" + pbbstream "github.com/streamingfast/pbgo/dfuse/bstream/v1" + "gotest.tools/assert" +) + +func TestCdCAdapter_AdaptJSON(t *testing.T) { + type fields struct { + generator GeneratorAtActionLevel + } + type args struct { + rawStep pbbstream.ForkStep + } + tests := []struct { + name string + file string + schema string + fields fields + args args + want []*kafka.Message + wantErr bool + }{ + { + name: "cdc-table", + file: "testdata/block-30080032.json", + schema: tableSchema(t, "eosio.nft.ft", "testdata/eosio.nft.ft.abi", "factory.a"), + fields: fields{ + generator: newTableGen4Test(t, "factory.a"), + }, + args: args{pbbstream.ForkStep_STEP_NEW}, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + byteValue := readFileFromTestdata(t, tt.file) + + block := &pbcodec.Block{} + // must delete rlimit_ops, valid_block_signing_authority_v2, active_schedule_v2 + err := json.Unmarshal(byteValue, block) + if err != nil { + t.Fatalf("Unmarshal() error: %v", err) + } + m := &CdCAdapter{ + topic: "test.topic", + saveBlock: saveBlockNoop, + generator: transaction2ActionsGenerator{ + actionLevelGenerator: tt.fields.generator, + topic: "test.topic", + headers: default_headers, + }, + headers: default_headers, + } + blockStep := BlockStep{ + blk: block, + step: tt.args.rawStep, + cursor: "123", + } + got, err := m.Adapt(blockStep) + if (err != nil) != tt.wantErr { + t.Errorf("CdCAdapter.Adapt() error = %v, wantErr %v", err, tt.wantErr) + return + } + kafkaMessage := got[0] + + assert.Equal(t, findHeader("content-type", kafkaMessage.Headers), "application/json") + assert.Equal(t, findHeader("ce_datacontenttype", kafkaMessage.Headers), "application/json") + }) + } +} + +func findHeader(name string, headers []kafka.Header) string { + + for _, header := range headers { + if header.Key == name { + return string(header.Value) + } + } + return "" +} + +func newTableGen4Test(t testing.TB, tableName string) TableGenerator { + var localABIFiles = map[string]string{ + "eosio.nft.ft": "testdata/eosio.nft.ft.abi", + } + abiFiles, err := LoadABIFiles(localABIFiles) + if err != nil { + t.Fatalf("LoadABIFiles() error: %v", err) + } + abiDecoder := NewABIDecoder(abiFiles, nil, context.Background()) + finder, _ := buildTableKeyExtractorFinder([]string{fmt.Sprintf("%s:s+k", tableName)}) + return TableGenerator{ + getExtractKey: finder, + abiCodec: NewJsonABICodec(abiDecoder, "eosio.nft.ft"), + } +} + +func tableSchema(t testing.TB, account string, abiFile string, tableName string) string { + abiSpec, err := LoadABIFile(account, abiFile) + if err != nil { + t.Fatalf("LoadABIFile(abiFile) error: %v", err) + } + schema, err := GenerateTableSchema(NamedSchemaGenOptions{ + Name: tableName, + AbiSpec: abiSpec, + }) + + if err != nil { + t.Fatalf("GenerateTableSchema() error: %v", err) + } + bytes, err := json.Marshal(schema) + if err != nil { + t.Fatalf("json.Marshal() error: %v", err) + } + return string(bytes) +} + +func TestCdCAdapter_Adapt_pb(t *testing.T) { + eos.NativeType = true + + tests := []struct { + name string + file string + account string + abis map[string]string + table string + nbMessages int + }{ + { + "accounts", + "testdata/block-49608395.pb.json", + "testdata/eosio.token.abi", + map[string]string{"eosio.token": "testdata/eosio.token.abi"}, + "accounts", + 2, + }, + { + "nft-factory", + "testdata/block-50705256.pb.json", + "testdata/eosio.nft.ft.abi", + map[string]string{"eosio.nft.ft": "testdata/eosio.nft.ft.abi"}, + "factory.a", + 1, + }, + { + "nft-factory-b", + "testdata/block-135283216.pb.json", + "testdata/eosio.nft.ft-4.0.6-snapshot.abi", + map[string]string{"eosio.nft.ft": "testdata/eosio.nft.ft-4.0.6-snapshot.abi"}, + "factory.b", + 1, + }, + { + "eosio.oracle", + "testdata/block-43922498.pb.json", + "testdata/eosio.oracle.abi", + map[string]string{"eosio.oracle": "testdata/eosio.oracle.abi"}, + "*", + 4, + }, + { + "eosio.token-chained-table", + "testdata/block-224785515.pb.json", + "eosio.token", + map[string]string{"eosio.token": "testdata/eosio.token-2.abi", "ultra.rgrab": "testdata/ultra.rgrab.abi"}, + "*", + 3, + }, + { + "eosio.token-chained-table", + "testdata/block-105048059.pb.json", + "eosio.token", + map[string]string{"eosio.token": "testdata/eosio.token-2.abi", "1aa2aa3aa4bx": "testdata/1aa2aa3aa4bx.abi"}, + "*", + 3, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + block := &pbcodec.Block{} + err := jsonpb.UnmarshalString(string(readFileFromTestdata(t, tt.file)), block) + if err != nil { + t.Fatalf("jsonpb.UnmarshalString(): %v", err) + } + + abiFiles, err := LoadABIFiles(tt.abis) + if err != nil { + t.Fatalf("LoadABIFiles() error: %v", err) + } + abiDecoder := NewABIDecoder(abiFiles, nil, context.Background()) + msg := MessageSchemaGenerator{ + Namespace: "test.dkafka", + Version: "1.2.3", + Account: tt.account, + } + // abi, _ := abiDecoder.abi(abiAccount, 0, false) + // schema, _ := msg.getTableSchema("accounts", abi) + // jsonSchema, err := json.Marshal(schema) + // fmt.Println(string(jsonSchema)) + finder, _ := buildTableKeyExtractorFinder([]string{fmt.Sprintf("%s:s+k", tt.table)}) + g := TableGenerator{ + getExtractKey: finder, + abiCodec: NewStreamedAbiCodec(&DfuseAbiRepository{ + overrides: abiDecoder.overrides, + abiCodecCli: abiDecoder.abiCodecCli, + context: abiDecoder.context, + }, msg.getTableSchema, srclient.CreateMockSchemaRegistryClient("mock://bench-adapter"), tt.account, "mock://bench-adapter", srclient.Forward), + } + a := &CdCAdapter{ + topic: "test.topic", + saveBlock: saveBlockNoop, + generator: transaction2ActionsGenerator{ + actionLevelGenerator: g, + topic: "test.topic", + headers: default_headers, + }, + headers: default_headers, + } + blockStep := BlockStep{ + blk: block, + step: pbbstream.ForkStep_STEP_NEW, + cursor: "123", + } + messages, err := a.Adapt(blockStep) + if err != nil { + t.Fatalf("Adapt() error: %v", err) + } + assert.Equal(t, len(messages), tt.nbMessages) + for _, m := range messages { + assert.Equal(t, findHeader("content-type", m.Headers), "application/avro") + assert.Equal(t, findHeader("ce_datacontenttype", m.Headers), "application/avro") + assert.Assert(t, findHeader("ce_dataschema", m.Headers) != "") + } + }) + } +} + +func TestCdCAdapter_Action_pb(t *testing.T) { + eos.NativeType = true + + tests := []struct { + name string + file string + abi string + actionExpression string + nbMessages int + }{ + { + "eosio.nft.ft", + "testdata/block-135283642.pb.json", + "eosio.nft.ft:testdata/eosio.nft.ft-4.0.6-snapshot.abi", + `{"*":"transaction_id"}`, + 1, + }, + { + "ultra.rgrab", + "testdata/block-220236206.pb.json", + "ultra.rgrab:testdata/ultra.rgrab.abi", + `{"*":"transaction_id"}`, + 1, + }, + { + "eosio.token", + "testdata/block-224793793.pb.json", + "eosio.token:testdata/eosio.token-2.abi", + `{"*":"first_auth_actor"}`, + 3, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + block := &pbcodec.Block{} + err := jsonpb.UnmarshalString(string(readFileFromTestdata(t, tt.file)), block) + if err != nil { + t.Fatalf("jsonpb.UnmarshalString(): %v", err) + } + + var localABIFiles = map[string]string{} + var abiAccount string + + if account, abiPath, err := ParseABIFileSpec(tt.abi); err != nil { + t.Fatalf("ParseABIFileSpec() fail to get ABI from: '%s'; %v", tt.abi, err) + } else { + abiAccount = account + localABIFiles[account] = abiPath + } + + abiFiles, err := LoadABIFiles(localABIFiles) + if err != nil { + t.Fatalf("LoadABIFiles() error: %v", err) + } + abiDecoder := NewABIDecoder(abiFiles, nil, context.Background()) + msg := MessageSchemaGenerator{ + Namespace: "test.dkafka", + Version: "1.2.3", + Account: abiAccount, + } + // abi, _ := abiDecoder.abi(abiAccount, 0, false) + // schema, _ := msg.getTableSchema("accounts", abi) + // jsonSchema, err := json.Marshal(schema) + // fmt.Println(string(jsonSchema)) + actionKeyExpressions, err := createCdcKeyExpressions(tt.actionExpression) + if err != nil { + t.Fatalf("createCdcKeyExpressions() error: %v", err) + } + g := ActionGenerator2{ + keyExtractors: actionKeyExpressions, + abiCodec: NewStreamedAbiCodec(&DfuseAbiRepository{ + overrides: abiDecoder.overrides, + abiCodecCli: abiDecoder.abiCodecCli, + context: abiDecoder.context, + }, msg.getActionSchema, srclient.CreateMockSchemaRegistryClient("mock://bench-adapter"), abiAccount, "mock://bench-adapter", srclient.Forward), + } + a := &CdCAdapter{ + topic: "test.topic", + saveBlock: saveBlockNoop, + generator: transaction2ActionsGenerator{ + actionLevelGenerator: g, + topic: "test.topic", + headers: default_headers, + }, + headers: default_headers, + } + blockStep := BlockStep{ + blk: block, + step: pbbstream.ForkStep_STEP_NEW, + cursor: "123", + } + messages, err := a.Adapt(blockStep) + if err != nil { + t.Fatalf("Adapt() error: %v", err) + } + assert.Equal(t, len(messages), tt.nbMessages) + fmt.Printf("messages size: %v\n", len(messages[0].Value)) + for _, m := range messages { + assert.Equal(t, findHeader("content-type", m.Headers), "application/avro") + assert.Equal(t, findHeader("ce_datacontenttype", m.Headers), "application/avro") + assert.Assert(t, findHeader("ce_dataschema", m.Headers) != "") + } + }) + } +} + +func abiResolve(p string) (account string, pt string) { + pt = p + account = strings.TrimRight(path.Base(pt), ".abi") + i := strings.Index(account, "-") + if i > -1 { + account = account[:i] + } + return +} + +func TestAbiResolve(t *testing.T) { + tests := []struct { + name string + path string + wantAccount string + wantPath string + }{ + { + name: "eosio.nft.ft versioned", + path: "testdata/eosio.nft.ft-4.0.6-snapshot.abi", + wantAccount: "eosio.nft.ft", + wantPath: "testdata/eosio.nft.ft-4.0.6-snapshot.abi", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if account, p := abiResolve(tt.path); account != tt.wantAccount || p != tt.wantPath { + t.Errorf("abiResolve() = (%v, %v), want (%v, %v)", account, p, tt.wantAccount, tt.wantPath) + } + }) + } +} diff --git a/adapter.go b/adapter.go new file mode 100644 index 0000000..69aa0e4 --- /dev/null +++ b/adapter.go @@ -0,0 +1,266 @@ +package dkafka + +import ( + "crypto/sha512" + "encoding/base64" + "encoding/json" + "fmt" + "io/ioutil" + "strings" + "time" + + "github.com/confluentinc/confluent-kafka-go/kafka" + pbcodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/codec/v1" + "github.com/golang/protobuf/jsonpb" + "github.com/golang/protobuf/proto" + "github.com/google/cel-go/cel" + "go.uber.org/zap" +) + +type SaveBlock = func(*pbcodec.Block) +type DecodeDBOps = func(in []*pbcodec.DBOp, blockNum uint32) (decodedDBOps []*decodedDBOp, err error) + +func saveBlockNoop(*pbcodec.Block) { + // does nothing +} + +// func marshalJSON(m proto.Message) ([]byte, error) { +// return json.Marshal(m) +// } + +// func saveBlockJSON(block *pbcodec.Block) { +// saveBlock(block, marshalJSON, "json") +// } + +func saveBlockJSONPB(block proto.Message) ([]byte, error) { + s, err := (&jsonpb.Marshaler{}).MarshalToString(block) + if err != nil { + return nil, err + } + return []byte(s), nil +} + +func saveBlockProto(block *pbcodec.Block) { + saveBlock(block, saveBlockJSONPB, "pb.json") +} + +type MarshalFunc func(proto.Message) ([]byte, error) + +func saveBlock(block *pbcodec.Block, marshal MarshalFunc, extension string) { + byteArray, err := marshal(block) + if err != nil { + zlog.Error("Fail to marshal to JSON incoming block", zap.Uint32("id", block.Number), zap.Error(err)) + } + // the WriteFile method returns an error if unsuccessful + fileName := fmt.Sprintf("block-%d.%s", block.Number, extension) + err = ioutil.WriteFile(fileName, byteArray, 0644) + // handle this error + if err != nil { + zlog.Error("Fail to write file", zap.String("file", fileName), zap.Error(err)) + } +} + +type adapter struct { + topic string + saveBlock SaveBlock + decodeDBOps DecodeDBOps + failOnUndecodableDBOP bool + generator Generator + // TODO merge all headers + headers []kafka.Header +} + +func newActionsAdapter( + topic string, + saveBlock SaveBlock, + decodeDBOps DecodeDBOps, + failOnUndecodableDBOP bool, + actionsConfJson string, + headers []kafka.Header, +) (*adapter, error) { + actionsConf := make(ActionsConf) + err := json.Unmarshal(json.RawMessage(actionsConfJson), &actionsConf) + if err != nil { + return nil, err + } + generator, err := NewActionsGenerator(actionsConf) + if err != nil { + return nil, err + } + return &adapter{ + topic, + saveBlock, + decodeDBOps, + failOnUndecodableDBOP, + generator, + headers, + }, nil +} + +func newAdapter( + topic string, + saveBlock SaveBlock, + decodeDBOps DecodeDBOps, + failOnUndecodableDBOP bool, + eventTypeProg cel.Program, + eventKeyProg cel.Program, + headers []kafka.Header, +) *adapter { + return &adapter{topic, saveBlock, decodeDBOps, failOnUndecodableDBOP, NewExpressionsGenerator(eventKeyProg, eventTypeProg), headers} +} + +func (m *adapter) Adapt(blkStep BlockStep) ([]*kafka.Message, error) { + blk := blkStep.blk + m.saveBlock(blk) + step := sanitizeStep(blkStep.step.String()) + + if blk.Number%100 == 0 { + zlog.Info("incoming block 1/100", zap.Uint32("block_num", blk.Number), zap.String("step", step), zap.Int("length_filtered_trx_traces", len(blk.FilteredTransactionTraces))) + } + if blk.Number%10 == 0 { + zlog.Debug("incoming block 1/10", zap.Uint32("block_num", blk.Number), zap.String("step", step), zap.Int("length_filtered_trx_traces", len(blk.FilteredTransactionTraces))) + } + msgs := make([]*kafka.Message, 0, 1) + blkTime := blk.MustTime().UTC() + // blkTimeStr := blkTime.Format("2006-01-02T15:04:05.9Z") + blkTimeStr := blkTime.Format(time.RFC3339) + blkTimeBytes := []byte(blkTimeStr) + + trxs := blk.TransactionTraces() + + for _, trx := range trxs { + transactionTracesReceived.Inc() + status := sanitizeStatus(trx.Receipt.Status.String()) + // manage correlation + correlation, err := getCorrelation(trx.ActionTraces) + if err != nil { + return nil, err + } + for _, act := range trx.ActionTraces { + if !act.FilteringMatched { + continue + } + actionTracesReceived.Inc() + var jsonData json.RawMessage + if act.Action.JsonData != "" { + jsonData = json.RawMessage(act.Action.JsonData) + } + + var authorizations []string + for _, auth := range act.Action.Authorization { + authorizations = append(authorizations, auth.Authorization()) + } + + var globalSeq uint64 + if act.Receipt != nil { + globalSeq = act.Receipt.GlobalSequence + } + + decodedDBOps, err := m.decodeDBOps(trx.DBOpsForAction(act.ExecutionIndex), blk.Number) + if err != nil { + if m.failOnUndecodableDBOP { + return nil, err + } + zlog.Warn("cannot decode dbops", zap.Uint32("block_num", blk.Number), zap.Error(err)) + } + + // generation + generations, err := m.generator.Apply(step, trx, + act, + decodedDBOps) + + if err != nil { + return nil, err + } + var source string + for _, entry := range m.headers { + if entry.Key == "ce_source" { + source = string(entry.Value) + } + } + if source == "" { + return nil, fmt.Errorf("ce_source is missing") + } + for _, generation := range generations { + eosioAction := event{ + BlockNum: blk.Number, + BlockID: blk.Id, + Status: status, + Executed: !trx.HasBeenReverted(), + Step: step, + Correlation: correlation, + TransactionID: trx.Id, + ActionInfo: ActionInfoDetails{ + Account: act.Account(), + Receiver: act.Receiver, + Action: act.Name(), + JSONData: &jsonData, + DBOps: generation.DecodedDBOps, + Authorization: authorizations, + GlobalSequence: globalSeq, + }, + } + + headers := append(m.headers, + kafka.Header{ + Key: "ce_id", + Value: hashString(fmt.Sprintf("%s%s%d%s%s%s", blk.Id, trx.Id, act.ExecutionIndex, generation.CeType, step, generation.Key)), + }, + kafka.Header{ + Key: "ce_type", + Value: []byte(generation.CeType), + }, + kafka.Header{ + Key: "ce_time", + Value: []byte(blkTimeBytes), + }, + kafka.Header{ + Key: "ce_blkstep", + Value: []byte(step), + }, + ) + msg := &kafka.Message{ + Key: []byte(generation.Key), + Headers: headers, + Value: eosioAction.JSON(), + TopicPartition: kafka.TopicPartition{ + Topic: &m.topic, + Partition: kafka.PartitionAny, + }, + } + msgs = append(msgs, msg) + } + } + } + return msgs, nil +} + +func hashString(data string) []byte { + h := sha512.New() + h.Write([]byte(data)) + return []byte(base64.StdEncoding.EncodeToString(h.Sum(nil))) +} + +func sanitizeStep(step string) string { + return strings.Title(strings.TrimPrefix(step, "STEP_")) +} +func sanitizeStatus(status string) string { + return strings.Title(strings.TrimPrefix(status, "TRANSACTIONSTATUS_")) +} + +func getCorrelation(actions []*pbcodec.ActionTrace) (correlation *Correlation, err error) { + for _, act := range actions { + if act.Account() == "ultra.tools" && act.Name() == "correlate" { + jsonString := act.Action.GetJsonData() + var out map[string]interface{} + err = json.Unmarshal([]byte(jsonString), &out) + if err != nil { + err = fmt.Errorf("decoding correlate action %q: %w", jsonString, err) + return + } + correlation = &Correlation{fmt.Sprint(out["payer"]), fmt.Sprint(out["correlation_id"])} + return + } + } + return +} diff --git a/adapter_test.go b/adapter_test.go new file mode 100644 index 0000000..d06bf0f --- /dev/null +++ b/adapter_test.go @@ -0,0 +1,447 @@ +package dkafka + +import ( + "context" + "encoding/json" + "io/ioutil" + "os" + "path" + "reflect" + "testing" + + "github.com/confluentinc/confluent-kafka-go/kafka" + pbcodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/codec/v1" + "github.com/eoscanada/eos-go" + "github.com/golang/protobuf/jsonpb" + "github.com/golang/protobuf/proto" + "github.com/riferrei/srclient" + pbbstream "github.com/streamingfast/pbgo/dfuse/bstream/v1" + "gotest.tools/assert" +) + +var default_headers = []kafka.Header{{ + Key: "ce_source", + Value: []byte("dkafka-test"), +}} + +func readFileFromTestdata(t testing.TB, file string) []byte { + f, err := os.Open(file) + if err != nil { + t.Fatalf("Open() error: %v", err) + } + defer f.Close() + // read block + byteValue, err := ioutil.ReadAll(f) + if err != nil { + t.Fatalf("ReadAll() error: %v", err) + } + return byteValue +} + +func Test_adapter_adapt(t *testing.T) { + eos.LegacyJSON4Asset = false + tests := []struct { + name string + file string + expected string + failOnUndecodableDBOP bool + actionBased bool + wantErr bool + }{ + { + "filter-out", + "testdata/block-30080030.json", + "", + true, + false, + false, + }, + { + "filter-in-expr", + "testdata/block-30080032.json", + "testdata/block-30080032-expected.json", + true, + false, + false, + }, + { + "filter-in-actions", + "testdata/block-30080032.json", + "testdata/block-30080032-expected.json", + true, + true, + false, + }, + } + for _, tt := range tests { + t.Run(path.Base(tt.name), func(t *testing.T) { + + byteValue := readFileFromTestdata(t, tt.file) + + block := &pbcodec.Block{} + // must delete rlimit_ops, valid_block_signing_authority_v2, active_schedule_v2 + err := json.Unmarshal(byteValue, block) + if err != nil { + t.Fatalf("Unmarshal() error: %v", err) + } + var localABIFiles = map[string]string{ + "eosio.nft.ft": "testdata/eosio.nft.ft.abi", + } + abiFiles, err := LoadABIFiles(localABIFiles) + if err != nil { + t.Fatalf("LoadABIFiles() error: %v", err) + } + abiDecoder := NewABIDecoder(abiFiles, nil, context.Background()) + eventTypeProg, err := exprToCelProgram("'TestType'") + if err != nil { + t.Fatalf("exprToCelProgram() error: %v", err) + } + eventKeyProg, err := exprToCelProgram("[transaction_id]") + if err != nil { + t.Fatalf("exprToCelProgram() error: %v", err) + } + var adp *adapter + if tt.actionBased { + adp, err = newActionsAdapter( + "test.topic", + saveBlockNoop, + abiDecoder.DecodeDBOps, + true, + `{"create":[{"key":"transaction_id", "type":"TestType"}]}`, + default_headers, + ) + if err != nil { + t.Fatalf("newActionsAdapter() error: %v", err) + return + } + } else { + adp = newAdapter( + "test.topic", + saveBlockNoop, + abiDecoder.DecodeDBOps, + true, + eventTypeProg, + eventKeyProg, + default_headers, + ) + } + blockStep := BlockStep{ + blk: block, + step: pbbstream.ForkStep_STEP_NEW, + cursor: "123", + } + if msg, err := adp.Adapt(blockStep); (err != nil) != tt.wantErr { + t.Errorf("adapter.adapt() error = %v, wantErr %v", err, tt.wantErr) + } else { + if tt.expected != "" { + byteValue := readFileFromTestdata(t, tt.expected) + + var expectedObjectMap map[string]interface{} + // must delete rlimit_ops, valid_block_signing_authority_v2, active_schedule_v2 + err := json.Unmarshal(byteValue, &expectedObjectMap) + if err != nil { + t.Fatalf("Unmarshal() error: %v", err) + } + + if rawJSON, err := messageToJSON(msg[0]); err != nil { + t.Errorf("messageToJSON() error: %v", err) + } else { + var actualObjectMap map[string]interface{} + if err := json.Unmarshal(rawJSON, &actualObjectMap); err != nil { + t.Fatalf("Unmarshal() error: %v", err) + } else { + if !reflect.DeepEqual(actualObjectMap, expectedObjectMap) { + t.Errorf("adapter.adapt() result diff\nactual:\n%s\nexpected:\n%s", string(rawJSON), string(byteValue)) + } + } + } + } + } + }) + } +} + +type AdapterType = int + +const ( + DEFAULT_ADAPTER AdapterType = iota + ACTION_ADAPTER + CDC_TABLE_ADAPTER + CDC_ACTION_ADAPTER + CDC_TABLE_ADAPTER_AVRO + CDC_ACTION_ADAPTER_AVRO +) + +func Benchmark_adapter_adapt(b *testing.B) { + tests := []struct { + name string + file string + actionBased AdapterType + }{ + { + "filter-out", + "testdata/block-30080030.json", + DEFAULT_ADAPTER, + }, + { + "filter-in", + "testdata/block-30080032.json", + DEFAULT_ADAPTER, + }, + { + "filter-in-actions", + "testdata/block-30080032.json", + ACTION_ADAPTER, + }, + { + "cdc-tables", + "testdata/block-30080032.json", + CDC_TABLE_ADAPTER, + }, + { + "cdc-actions", + "testdata/block-30080032.json", + CDC_ACTION_ADAPTER, + }, + { + "cdc-tables-avro", + "testdata/block-30080032.json", + CDC_TABLE_ADAPTER_AVRO, + }, + { + "cdc-actions-avro", + "testdata/block-30080032.json", + CDC_ACTION_ADAPTER_AVRO, + }, + } + + for _, tt := range tests { + f, err := os.Open(tt.file) + if err != nil { + b.Fatalf("Open() error: %v", err) + } + defer f.Close() + // read block + byteValue, err := ioutil.ReadAll(f) + if err != nil { + b.Fatalf("ReadAll() error: %v", err) + } + block := &pbcodec.Block{} + // must delete rlimit_ops, valid_block_signing_authority_v2, active_schedule_v2 + err = json.Unmarshal(byteValue, block) + if err != nil { + b.Fatalf("Unmarshal() error: %v", err) + } + var localABIFiles = map[string]string{ + "eosio.nft.ft": "testdata/eosio.nft.ft.abi", + } + abiFiles, err := LoadABIFiles(localABIFiles) + if err != nil { + b.Fatalf("LoadABIFiles() error: %v", err) + } + abiDecoder := NewABIDecoder(abiFiles, nil, context.Background()) + eventTypeProg, err := exprToCelProgram("'TestType'") + if err != nil { + b.Fatalf("exprToCelProgram() error: %v", err) + } + eventKeyProg, err := exprToCelProgram("[transaction_id]") + if err != nil { + b.Fatalf("exprToCelProgram() error: %v", err) + } + var adp Adapter + switch adapterType := tt.actionBased; adapterType { + case ACTION_ADAPTER: + adp, err = newActionsAdapter( + "test.topic", + saveBlockNoop, + abiDecoder.DecodeDBOps, + true, + `{"create":[{"key":"transaction_id", "type":"TestType"}]}`, + default_headers, + ) + if err != nil { + b.Fatalf("newActionsAdapter() error: %v", err) + return + } + case DEFAULT_ADAPTER: + adp = newAdapter( + "test.topic", + saveBlockNoop, + abiDecoder.DecodeDBOps, + true, + eventTypeProg, + eventKeyProg, + nil, + ) + case CDC_TABLE_ADAPTER: + finder, _ := buildTableKeyExtractorFinder([]string{"factory.a:k"}) + adp = &CdCAdapter{ + topic: "test.topic", + saveBlock: saveBlockNoop, + generator: transaction2ActionsGenerator{ + actionLevelGenerator: TableGenerator{ + getExtractKey: finder, + abiCodec: NewJsonABICodec(abiDecoder, "eosio.nft.ft"), + }, + abiCodec: NewJsonABICodec(abiDecoder, "eosio.token"), + headers: default_headers, + topic: "test.topic", + }, + headers: default_headers, + } + case CDC_ACTION_ADAPTER: + actionKeyExpressions, err := createCdcKeyExpressions(`{"create":"transaction_id"}`) + if err != nil { + b.Fatalf("createCdcKeyExpressions() error: %v", err) + return + } + adp = &CdCAdapter{ + topic: "test.topic", + saveBlock: saveBlockNoop, + generator: transaction2ActionsGenerator{ + actionLevelGenerator: &ActionGenerator2{ + keyExtractors: actionKeyExpressions, + abiCodec: NewJsonABICodec(abiDecoder, "eosio.nft.ft"), + }, + abiCodec: NewJsonABICodec(abiDecoder, "eosio.token"), + headers: default_headers, + topic: "test.topic", + }, + + headers: default_headers, + } + case CDC_TABLE_ADAPTER_AVRO: + msg := MessageSchemaGenerator{ + Namespace: "test.dkafka", + Version: "1.2.3", + Account: "eosio.nft.ft", + } + abiCodec := NewStreamedAbiCodec(&DfuseAbiRepository{ + overrides: abiDecoder.overrides, + abiCodecCli: abiDecoder.abiCodecCli, + context: abiDecoder.context, + }, msg.getTableSchema, srclient.CreateMockSchemaRegistryClient("mock://bench-adapter"), msg.Account, "mock://bench-adapter", srclient.Forward) + abiCodec.GetCodec(CodecId{"eosio.nft.ft", "factory.a"}, 0) + finder, _ := buildTableKeyExtractorFinder([]string{"factory.a:k"}) + adp = &CdCAdapter{ + topic: "test.topic", + saveBlock: saveBlockNoop, + generator: transaction2ActionsGenerator{ + actionLevelGenerator: TableGenerator{ + getExtractKey: finder, + abiCodec: abiCodec, + }, + abiCodec: abiCodec, + headers: default_headers, + }, + abiCodec: abiCodec, + headers: default_headers, + } + case CDC_ACTION_ADAPTER_AVRO: + actionKeyExpressions, err := createCdcKeyExpressions(`{"create":"transaction_id"}`) + if err != nil { + b.Fatalf("createCdcKeyExpressions() error: %v", err) + return + } + msg := MessageSchemaGenerator{ + Namespace: "test.dkafka", + Version: "1.2.3", + Account: "eosio.nft.ft", + } + abiCodec := NewStreamedAbiCodec(&DfuseAbiRepository{ + overrides: abiDecoder.overrides, + abiCodecCli: abiDecoder.abiCodecCli, + context: abiDecoder.context, + }, msg.getActionSchema, srclient.CreateMockSchemaRegistryClient("mock://bench-adapter"), msg.Account, "mock://bench-adapter", srclient.Forward) + abiCodec.GetCodec(CodecId{"eosio.nft.ft", "create"}, 0) + adp = &CdCAdapter{ + topic: "test.topic", + saveBlock: saveBlockNoop, + generator: transaction2ActionsGenerator{ + actionLevelGenerator: &ActionGenerator2{ + keyExtractors: actionKeyExpressions, + abiCodec: abiCodec, + }, + abiCodec: abiCodec, + headers: default_headers, + }, + abiCodec: abiCodec, + headers: default_headers, + } + } + blockStep := BlockStep{ + blk: block, + step: pbbstream.ForkStep_STEP_NEW, + cursor: "123", + } + b.Run(tt.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + adp.Adapt(blockStep) + } + }) + } +} + +func readFileFromTestdataProto(t testing.TB, file string, m proto.Message) { + t.Helper() + f, err := os.Open(file) + if err != nil { + t.Fatalf("Open() error: %v", err) + } + err = jsonpb.Unmarshal(f, m) + if err != nil { + t.Fatalf("jsonpb.Unmarshal() error: %v", err) + } +} + +func Test_adapter_correlation_id(t *testing.T) { + block := &pbcodec.Block{} + readFileFromTestdataProto(t, "testdata/block-49608395.pb.json", block) + var localABIFiles = map[string]string{ + "eosio.nft.ft": "testdata/eosio.nft.ft.abi", + "eosio.token": "testdata/eosio.token.abi", + } + abiFiles, err := LoadABIFiles(localABIFiles) + if err != nil { + t.Fatalf("LoadABIFiles() error: %v", err) + } + abiDecoder := NewABIDecoder(abiFiles, nil, context.Background()) + finder, _ := buildTableKeyExtractorFinder([]string{"accounts:s+k"}) + abiCodec := NewJsonABICodec(abiDecoder, "eosio.token") + adp := &CdCAdapter{ + topic: "test.topic", + saveBlock: saveBlockNoop, + generator: transaction2ActionsGenerator{ + actionLevelGenerator: TableGenerator{ + getExtractKey: finder, + abiCodec: abiCodec, + }, + abiCodec: abiCodec, + headers: default_headers, + topic: "test.topic", + }, + abiCodec: abiCodec, + headers: default_headers, + } + blockStep := BlockStep{ + blk: block, + step: pbbstream.ForkStep_STEP_NEW, + cursor: "123", + } + if msgs, err := adp.Adapt(blockStep); err != nil { + t.Errorf("adapter.adapt() error = %v", err) + } else { + assert.Equal(t, len(msgs), 2, "should produce 2 messages") + for _, msg := range msgs { + value := make(map[string]interface{}) + err := json.Unmarshal(msg.Value, &value) + if err != nil { + t.Errorf("json.Unmarshal() error: %v", err) + } + context := value["context"].(map[string]interface{}) + correlation := context["correlation"].(map[string]interface{}) + assert.Equal(t, correlation["id"], "ed19191b-3962-4c58-9dee-f41398866ee1", "should provide the correlation id") + } + } + +} diff --git a/app.go b/app.go index 3ee52b0..97f1c17 100644 --- a/app.go +++ b/app.go @@ -6,15 +6,19 @@ import ( "encoding/json" "fmt" "io" + "os" "strings" "time" "github.com/confluentinc/confluent-kafka-go/kafka" - "github.com/dfuse-io/dfuse-eosio/filtering" pbabicodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/abicodec/v1" pbcodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/codec/v1" + "github.com/dfuse-io/dkafka/action" + "github.com/dfuse-io/dkafka/table" "github.com/eoscanada/eos-go" "github.com/golang/protobuf/ptypes" + "github.com/google/cel-go/cel" + "github.com/riferrei/srclient" "github.com/streamingfast/bstream/forkable" "github.com/streamingfast/dgrpc" pbbstream "github.com/streamingfast/pbgo/dfuse/bstream/v1" @@ -28,15 +32,21 @@ import ( "github.com/streamingfast/shutter" ) +const TABLES_CDC_TYPE = "tables" +const ACTIONS_CDC_TYPE = "actions" +const TRANSACTION_CDC_TYPE = "transactions" + type Config struct { DfuseGRPCEndpoint string DfuseToken string DryRun bool // do not connect to Kafka, just print to stdout BatchMode bool + Capture bool StartBlockNum int64 StopBlockNum uint64 StateFile string + Force bool KafkaEndpoints string KafkaSSLEnable bool @@ -44,23 +54,63 @@ type Config struct { KafkaSSLAuth bool KafkaSSLClientCertFile string KafkaSSLClientKeyFile string + KafkaCompressionType string + KafkaCompressionLevel int + KafkaMessageMaxBytes int KafkaCursorConsumerGroupID string KafkaTransactionID string CommitMinDelay time.Duration - IncludeFilterExpr string KafkaTopic string KafkaCursorTopic string KafkaCursorPartition int32 EventSource string - EventKeysExpr string - EventTypeExpr string - EventExtensions map[string]string + + IncludeFilterExpr string + EventKeysExpr string + EventTypeExpr string + ActionsExpr string LocalABIFiles map[string]string ABICodecGRPCAddr string FailOnUndecodableDBOP bool + + CdCType string + Account string + ActionExpressions string + TableNames []string + Executed bool + Irreversible bool + SkipDbOps bool + + Codec string + SchemaRegistryURL string + SchemaNamespace string + SchemaMajorVersion uint + SchemaVersion string + Compatibility string +} + +var compatibilityMap = map[string]srclient.CompatibilityLevel{ + srclient.Backward.String(): srclient.Backward, + srclient.Forward.String(): srclient.Forward, + srclient.Full.String(): srclient.Full, + srclient.None.String(): srclient.None, + srclient.ForwardTransitive.String(): srclient.ForwardTransitive, + srclient.BackwardTransitive.String(): srclient.BackwardTransitive, + srclient.FullTransitive.String(): srclient.FullTransitive, +} + +func (c *Config) getCompatibility() (srclient.CompatibilityLevel, error) { + if c.Compatibility == "" { + return srclient.Forward, nil // default compatibility + } + if compat, ok := compatibilityMap[c.Compatibility]; ok { + return compat, nil + } + return srclient.Forward, fmt.Errorf("invalid compatibility level: %s", c.Compatibility) + } type App struct { @@ -76,9 +126,8 @@ func New(config *Config) *App { } } -func (a *App) Run() error { +func (a *App) Run() (err error) { go startPrometheusMetrics("/metrics", ":9102") - // get and setup the dfuse fetcher that gets a stream of blocks, includes the filter, will include the auth token resolver/refresher addr := a.config.DfuseGRPCEndpoint plaintext := strings.Contains(addr, "*") @@ -94,32 +143,14 @@ func (a *App) Run() error { credential := oauth.NewOauthAccess(&oauth2.Token{AccessToken: a.config.DfuseToken, TokenType: "Bearer"}) dialOptions = append(dialOptions, grpc.WithPerRPCCredentials(credential)) } - conn, err := grpc.Dial(addr, - dialOptions..., - ) - if err != nil { - return fmt.Errorf("connecting to grpc address %s: %w", addr, err) - } - - client := pbbstream.NewBlockStreamV2Client(conn) - req := &pbbstream.BlocksRequestV2{ - IncludeFilterExpr: a.config.IncludeFilterExpr, - StartBlockNum: a.config.StartBlockNum, - StopBlockNum: a.config.StopBlockNum, + var saveBlock SaveBlock + saveBlock = saveBlockNoop + if a.config.Capture { + saveBlock = saveBlockProto } - conf := createKafkaConfig(a.config) - - var producer *kafka.Producer - if !a.config.BatchMode || !a.config.DryRun { - producer, err = getKafkaProducer(conf, a.config.KafkaTransactionID) - if err != nil { - return fmt.Errorf("getting kafka producer: %w", err) - } - } - - var abiFiles map[string]*eos.ABI + var abiFiles map[string]*ABI if len(a.config.LocalABIFiles) != 0 { abiFiles, err = LoadABIFiles(a.config.LocalABIFiles) if err != nil { @@ -136,269 +167,590 @@ func (a *App) Run() error { abiCodecClient = pbabicodec.NewDecoderClient(abiCodecConn) } + source := a.config.EventSource + if len(source) == 0 { + if hostname, err := os.Hostname(); err == nil { + source = hostname + } else { + zlog.Warn("cannot get host name", zap.Error(err)) + // use generic name + source = "dkafka" + } + } + + zlog.Info("event source", zap.String("ce_source", source)) + sourceHeader := kafka.Header{ + Key: "ce_source", + Value: []byte(source), + } + specHeader := kafka.Header{ + Key: "ce_specversion", + Value: []byte("1.0"), + } + + headers := []kafka.Header{ + sourceHeader, + specHeader, + } + + ctx, cancel := context.WithCancel(context.Background()) + a.OnTerminating(func(_ error) { + cancel() + }) + out := make(chan error, 1) + closeOutChannel := func() { + zlog.Info("close error channel") + close(out) + } + defer closeOutChannel() + var producer *kafka.Producer + if !a.config.DryRun { + producer, err = getKafkaProducer(createKafkaConfigForMessageProducer(a.config)) + if err != nil { + return fmt.Errorf("cannot get kafka producer: %w", err) + } + go func() { + firedError := false + fireError := func(msg string, err error) { + zlog.Debug("fire error", zap.String("msg", msg), zap.Bool("already", firedError)) + if !firedError { + firedError = true + zlog.Error(msg, zap.Error(err)) + out <- err + } + } + for e := range producer.Events() { + switch ev := e.(type) { + case *kafka.Message: + // The message delivery report, indicating success or + // permanent failure after retries have been exhausted. + // Application level retries won't help since the client + // is already configured to do that. + m := ev + if m.TopicPartition.Error != nil { + err := m.TopicPartition.Error + fireError("Delivery failed", err) + } else { + zlog.Debug("Delivered message", zap.Stringp("topic", m.TopicPartition.Topic), zap.Int32("partition", m.TopicPartition.Partition), zap.Int64("offset", int64(m.TopicPartition.Offset))) + } + case kafka.Error: + // Generic client instance-level errors, such as + // broker connection failures, authentication issues, etc. + // + // These errors should generally be considered informational + // as the underlying client will automatically try to + // recover from any errors encountered, the application + // does not need to take action on them. + fireError("Kafka client fail", ev) + default: + zlog.Debug("Ignored producer event", zap.Stringer("event", ev.(fmt.Stringer))) + } + } + }() + closeProducer := func() { + zlog.Info("close kafka producer") + producer.Close() + } + defer closeProducer() + } zlog.Info("setting up ABIDecoder") - abiDecoder := NewABIDecoder(abiFiles, abiCodecClient) + abiDecoder := NewABIDecoder(abiFiles, abiCodecClient, ctx) if abiDecoder.IsNOOP() && a.config.FailOnUndecodableDBOP { - return fmt.Errorf("Invalid config: no abicodec GRPC address and no local ABI file has been set, but fail-on-undecodable-db-op is enabled") + return fmt.Errorf("invalid config: no abicodec GRPC address and no local ABI file has been set, but fail-on-undecodable-db-op is enabled") } - var cp checkpointer - if a.config.BatchMode { - zlog.Info("running in batch mode, ignoring cursors") - cp = &nilCheckpointer{} + var appCtx appCtx + if a.config.CdCType != "" { + appCtx, err = a.NewCDCCtx(ctx, producer, headers, abiDecoder, saveBlock) } else { - cp = newKafkaCheckpointer(conf, a.config.KafkaCursorTopic, a.config.KafkaCursorPartition, a.config.KafkaTopic, a.config.KafkaCursorConsumerGroupID, producer) - - cursor, err := cp.Load() - switch err { - case NoCursorErr: - zlog.Info("running in live mode, no cursor found: starting from beginning", zap.Int64("start_block_num", a.config.StartBlockNum)) - case nil: - c, err := forkable.CursorFromOpaque(cursor) - if err != nil { - zlog.Error("cannot decode cursor", zap.Error(err)) - return err - } - zlog.Info("running in live mode, found cursor", - zap.String("cursor", cursor), - zap.Stringer("plain_cursor", c), - zap.Stringer("cursor_block", c.Block), - zap.Stringer("cursor_head_block", c.HeadBlock), - zap.Stringer("cursor_LIB", c.LIB), - ) - req.StartCursor = cursor - default: - return fmt.Errorf("error loading cursor: %w", err) - } + appCtx, err = a.NewLegacyCtx(ctx, producer, headers, abiDecoder, saveBlock) } - if irreversibleOnly { - req.ForkSteps = []pbbstream.ForkStep{pbbstream.ForkStep_STEP_IRREVERSIBLE} + if err != nil { + return err } - var s sender if a.config.DryRun { - s = &dryRunSender{} - } else { - s, err = getKafkaSender(producer, cp, a.config.KafkaTransactionID != "") - if err != nil { - return err - } + appCtx.sender = &DryRunSender{} } + req := NewRequest(appCtx.filter, a.config.StartBlockNum, a.config.StopBlockNum, appCtx.cursor, a.config.Irreversible) - ctx, cancel := context.WithCancel(context.Background()) - a.OnTerminating(func(_ error) { - cancel() - }) + zlog.Debug("Connect to dfuse grpc", zap.String("address", addr), zap.Any("options", dialOptions)) + conn, err := grpc.Dial(addr, + dialOptions..., + ) + if err != nil { + return fmt.Errorf("connecting to grpc address %s: %w", addr, err) + } + zlog.Debug("Create streaming client") + client := pbbstream.NewBlockStreamV2Client(conn) + + zlog.Info("Filter blocks", zap.Any("request", req)) executor, err := client.Blocks(ctx, req) if err != nil { return fmt.Errorf("requesting blocks from dfuse firehose: %w", err) } + return iterate(ctx, cancel, appCtx, a.config.CommitMinDelay, executor, out) +} - // setup the transformer, that will transform incoming blocks +type appCtx struct { + adapter Adapter + filter string + sender Sender + cursor string +} - eventTypeProg, err := exprToCelProgram(a.config.EventTypeExpr) - if err != nil { - return fmt.Errorf("cannot parse event-type-expr: %w", err) - } - eventKeyProg, err := exprToCelProgram(a.config.EventKeysExpr) - if err != nil { - return fmt.Errorf("cannot parse event-keys-expr: %w", err) +func (a *App) NewCDCCtx(ctx context.Context, producer *kafka.Producer, headers []kafka.Header, abiDecoder *ABIDecoder, saveBlock SaveBlock) (appCtx, error) { + var adapter Adapter + var filter string + var cursor string + var abiCodec ABICodec + var generator GeneratorAtTransactionLevel + var err error + eos.LegacyJSON4Asset = false + eos.NativeType = true + appCtx := appCtx{} + if cursor, err = a.loadCursor(); err != nil { + return appCtx, fmt.Errorf("failed to load cursor at startup time for cdc on %s with error: %w", a.config.CdCType, err) } - var extensions []*extension - for k, v := range a.config.EventExtensions { - prog, err := exprToCelProgram(v) + switch cdcType := a.config.CdCType; cdcType { + case TABLES_CDC_TYPE: + msg := MessageSchemaGenerator{ + Namespace: a.config.SchemaNamespace, + MajorVersion: a.config.SchemaMajorVersion, + Version: a.config.SchemaVersion, + Account: a.config.Account, + } + abiCodec, err = a.config.newABICodec( + abiDecoder, + msg.getTableSchema, + NewStreamedAbiCodec, + ) if err != nil { - return fmt.Errorf("cannot parse event-extension: %w", err) + return appCtx, err + } + filter = addAllABIFilter(table.Filter(a.config.Account)) + var finder TableKeyExtractorFinder + if finder, err = buildTableKeyExtractorFinder(a.config.TableNames); err != nil { + return appCtx, err + } + generator = transaction2ActionsGenerator{ + actionLevelGenerator: TableGenerator{ + getExtractKey: finder, + abiCodec: abiCodec, + }, + abiCodec: abiCodec, + headers: headers, + topic: a.config.KafkaTopic, + account: a.config.Account, + } + case ACTIONS_CDC_TYPE: + filter = addAccountABIFilter(action.Filter(a.config.Account), a.config.Account) + actionKeyExpressions, err := createCdcKeyExpressions(a.config.ActionExpressions) + if err != nil { + return appCtx, err + } + msg := MessageSchemaGenerator{ + Namespace: a.config.SchemaNamespace, + MajorVersion: a.config.SchemaMajorVersion, + Version: a.config.SchemaVersion, + Account: a.config.Account, + } + abiCodec, err = a.config.newABICodec( + abiDecoder, + msg.getActionSchema, + NewStreamedAbiCodec, + ) + if err != nil { + return appCtx, err + } + generator = transaction2ActionsGenerator{ + actionLevelGenerator: ActionGenerator2{ + keyExtractors: actionKeyExpressions, + abiCodec: abiCodec, + skipDbOps: a.config.SkipDbOps, + }, + abiCodec: abiCodec, + headers: headers, + topic: a.config.KafkaTopic, + account: a.config.Account, } - extensions = append(extensions, &extension{ - name: k, - expr: v, - prog: prog, - }) - - } - sourceHeader := kafka.Header{ - Key: "ce_source", - Value: []byte(a.config.EventSource), + case TRANSACTION_CDC_TYPE: + filter = "" + msg := MessageSchemaGenerator{ + Namespace: a.config.SchemaNamespace, + MajorVersion: a.config.SchemaMajorVersion, + Version: a.config.SchemaVersion, + Account: a.config.Account, + } + abiCodec, err = a.config.newABICodec( + abiDecoder, + msg.getNoopSchema, + NewStreamedAbiCodecWithTransaction, + ) + if err != nil { + return appCtx, err + } + generator = transactionGenerator{ + topic: a.config.KafkaTopic, + headers: headers, + abiCodec: abiCodec, + } + default: + return appCtx, fmt.Errorf("unsupported CDC type %s", cdcType) } - specHeader := kafka.Header{ - Key: "ce_specversion", - Value: []byte("1.0"), + adapter = &CdCAdapter{ + topic: a.config.KafkaTopic, + saveBlock: saveBlock, + headers: headers, + generator: generator, + abiCodec: abiCodec, } - contentTypeHeader := kafka.Header{ - Key: "content-type", - Value: []byte("application/json"), + appCtx.adapter = adapter + appCtx.cursor = cursor + appCtx.filter = addExecutedFilter(filter, a.config.Executed) + appCtx.sender = NewFastSender(ctx, producer, a.config.KafkaTopic, headers, abiCodec) + return appCtx, nil +} + +func buildTableKeyExtractorFinder(tableNamesConfig []string) (finder TableKeyExtractorFinder, err error) { + tableNames := make(map[string]ExtractKey) + for _, name := range tableNamesConfig { + kv := strings.SplitN(name, ":", -1) + if len(kv) > 2 { + err = fmt.Errorf("unsupported table key extractor pattern: %s, on support {|*}[:{k|s|s+k}]", name) + return + } + var ek ExtractKey = extractFullKey + if len(kv) == 2 { + switch kv[1] { + case "k": + ek = extractPrimaryKey + case "s+k": + ek = extractFullKey + case "s": + ek = extractScope + default: + err = fmt.Errorf("unsupported table key extractor pattern: %s, on support {|*}[:{k|s|s+k}]", name) + return + } + } + tableNames[kv[0]] = ek } - dataContentTypeHeader := kafka.Header{ - Key: "ce_datacontenttype", - Value: []byte("application/json"), + if extractKey, wildcardFound := tableNames["*"]; wildcardFound { + + if len(tableNames) == 1 { + finder = func(tableName string) (ExtractKey, bool) { + return extractKey, true + } + } else { + finder = func(tableName string) (extract ExtractKey, found bool) { + extract, found = tableNames[tableName] + if !found { + extract = extractKey + found = true + } + return + } + } + } else { + finder = func(tableName string) (extract ExtractKey, found bool) { + extract, found = tableNames[tableName] + return + } } - // loop: receive block, transform block, send message... - for { - msg, err := executor.Recv() + return +} + +func createCdcKeyExpressions(cdcExpression string) (finder ActionKeyExtractorFinder, err error) { + var cdcProgramByKeys map[string]cel.Program + cdcExpressionMap := make(map[string]string) + var rawJSON = json.RawMessage(cdcExpression) + if err = json.Unmarshal(rawJSON, &cdcExpressionMap); err != nil { + return + } + cdcProgramByKeys = make(map[string]cel.Program) + for k, v := range cdcExpressionMap { + var prog cel.Program + prog, err = exprToCelProgramWithEnv(v, ActionDeclarations) if err != nil { - if err == io.EOF { - return nil + return + } + cdcProgramByKeys[k] = prog + } + if wildcardProgram, wildcardFound := cdcProgramByKeys["*"]; wildcardFound { + if len(cdcProgramByKeys) == 1 { + finder = func(actionName string) (cel.Program, bool) { + return wildcardProgram, true + } + } else { + finder = func(actionName string) (prog cel.Program, found bool) { + prog, found = cdcProgramByKeys[actionName] + if !found { + prog = wildcardProgram + found = true + } + return } - return fmt.Errorf("error on receive: %w", err) } - - blk := &pbcodec.Block{} - if err := ptypes.UnmarshalAny(msg.Block, blk); err != nil { - return fmt.Errorf("decoding any of type %q: %w", msg.Block.TypeUrl, err) + } else { + finder = func(actionName string) (prog cel.Program, found bool) { + prog, found = cdcProgramByKeys[actionName] + return } - blocksReceived.Inc() - step := sanitizeStep(msg.Step.String()) + } + return +} - if blk.Number%100 == 0 { - zlog.Info("incoming block 1/100", zap.Uint32("blk_number", blk.Number), zap.String("step", step), zap.Int("length_filtered_trx_traces", len(blk.FilteredTransactionTraces))) - } - if blk.Number%10 == 0 { - zlog.Debug("incoming block 1/10", zap.Uint32("blk_number", blk.Number), zap.String("step", step), zap.Int("length_filtered_trx_traces", len(blk.FilteredTransactionTraces))) +func LoadCursorFromCursorTopic(config *Config, cp checkpointer) (string, error) { + cursor, err := cp.Load() + switch err { + case ErrNoCursor: + zlog.Info("no cursor found in cursor topic", zap.String("cursor_topic", config.KafkaCursorTopic)) + return "", nil + case nil: + c, err := forkable.CursorFromOpaque(cursor) + if err != nil { + zlog.Error("cannot decode cursor", zap.String("cursor", cursor), zap.Error(err)) + return "", err } + zlog.Info("running in live mode, found cursor from cursor topic", + zap.String("cursor", cursor), + zap.Stringer("plain_cursor", c), + zap.Stringer("cursor_block", c.Block), + zap.Stringer("cursor_head_block", c.HeadBlock), + zap.Stringer("cursor_LIB", c.LIB), + zap.String("cursor_topic", config.KafkaCursorTopic), + ) + default: + return cursor, fmt.Errorf("error loading cursor: %w", err) + } + return cursor, nil +} - for _, trx := range blk.TransactionTraces() { - transactionTracesReceived.Inc() - status := sanitizeStatus(trx.Receipt.Status.String()) - memoizableTrxTrace := &filtering.MemoizableTrxTrace{TrxTrace: trx} - for _, act := range trx.ActionTraces { - if !act.FilteringMatched { - continue - } - actionTracesReceived.Inc() - var jsonData json.RawMessage - if act.Action.JsonData != "" { - jsonData = json.RawMessage(act.Action.JsonData) - } - activation := filtering.NewActionTraceActivation( - act, - memoizableTrxTrace, - msg.Step.String(), - ) - - var auths []string - for _, auth := range act.Action.Authorization { - auths = append(auths, auth.Authorization()) - } +func (a *App) loadCursor() (cursor string, err error) { + if a.config.Force { + zlog.Info("Force option activated skip loading cursor", zap.String("topic", a.config.KafkaTopic)) + return + } + zlog.Info("try to find previous position from message topic", zap.String("topic", a.config.KafkaTopic)) + cursor, err = LoadCursor(createKafkaConfig(a.config), a.config.KafkaTopic) + if err != nil { + return "", fmt.Errorf("fail to load cursor on topic: %s, due to: %w", a.config.KafkaTopic, err) + } + // UOD-1290 load cursor from legacy cursor topic for dkafka migration + if cursor == "" && a.config.KafkaCursorTopic != "" { + zlog.Info("no cursor in message topic try to load it from legacy cursor topic...", zap.String("topic_cursor", a.config.KafkaCursorTopic)) + cp := newKafkaCheckpointer(createKafkaConfig(a.config), a.config.KafkaCursorTopic, a.config.KafkaCursorPartition, a.config.KafkaTopic, a.config.KafkaCursorConsumerGroupID) + if cursor, err = LoadCursorFromCursorTopic(a.config, cp); err != nil { + return "", fmt.Errorf("fail to load cursor for legacy topic: %s, due to: %w", a.config.KafkaCursorTopic, err) + } + } else { + zlog.Info("no cursor topic specified skip loading position from it") + } + return +} - var globalSeq uint64 - if act.Receipt != nil { - globalSeq = act.Receipt.GlobalSequence - } +func (a *App) NewLegacyCtx(ctx context.Context, producer *kafka.Producer, headers []kafka.Header, abiDecoder *ABIDecoder, saveBlock SaveBlock) (appCtx, error) { + var adapter Adapter + var filter string = a.config.IncludeFilterExpr + var sender Sender + var cursor string + var err error + eos.LegacyJSON4Asset = true + eos.NativeType = false + appCtx := appCtx{} + + if cursor, err = a.loadCursor(); err != nil { + return appCtx, fmt.Errorf("failed to load cursor at startup time for json publish message with error: %w", err) + } - decodedDBOps, err := abiDecoder.DecodeDBOps(trx.DBOpsForAction(act.ExecutionIndex), blk.Number) - if err != nil { - if a.config.FailOnUndecodableDBOP { - return err - } - zlog.Warn("cannot decode dbops", zap.Uint32("block_number", blk.Number), zap.Error(err)) - } - eosioAction := event{ - BlockNum: blk.Number, - BlockID: blk.Id, - Status: status, - Executed: !trx.HasBeenReverted(), - Step: step, - TransactionID: trx.Id, - ActionInfo: ActionInfo{ - Account: act.Account(), - Receiver: act.Receiver, - Action: act.Name(), - JSONData: &jsonData, - DBOps: decodedDBOps, - Authorization: auths, - GlobalSequence: globalSeq, - }, - } + headers = append(headers, + kafka.Header{ + Key: "content-type", + Value: []byte("application/json"), + }, + kafka.Header{ + Key: "ce_datacontenttype", + Value: []byte("application/json"), + }, + ) + if a.config.ActionsExpr != "" { + adapter, err = newActionsAdapter(a.config.KafkaTopic, + saveBlock, + abiDecoder.DecodeDBOps, + a.config.FailOnUndecodableDBOP, + a.config.ActionsExpr, + headers, + ) + if err != nil { + return appCtx, err + } + } else { + eventTypeProg, err := exprToCelProgram(a.config.EventTypeExpr) + if err != nil { + return appCtx, fmt.Errorf("cannot parse event-type-expr: %w", err) + } + eventKeyProg, err := exprToCelProgram(a.config.EventKeysExpr) + if err != nil { + return appCtx, fmt.Errorf("cannot parse event-keys-expr: %w", err) + } + adapter = newAdapter( + a.config.KafkaTopic, + saveBlock, + abiDecoder.DecodeDBOps, + a.config.FailOnUndecodableDBOP, + eventTypeProg, + eventKeyProg, + headers, + ) + } + a.config.Codec = JsonCodec + abiCodec, err := a.config.newABICodec( + abiDecoder, + func(s string, a *ABI) (MessageSchema, error) { + return MessageSchema{}, fmt.Errorf("json message publisher does not support schema generation. Requested schema: %s", s) + }, + NewStreamedAbiCodec, + ) + sender = NewFastSender(ctx, producer, a.config.KafkaTopic, headers, abiCodec) + if err != nil { + return appCtx, err + } - eventType, err := evalString(eventTypeProg, activation) - if err != nil { - return fmt.Errorf("error eventtype eval: %w", err) - } + appCtx.adapter = adapter + appCtx.cursor = cursor + appCtx.filter = filter + appCtx.sender = sender + return appCtx, nil +} - extensionsKV := make(map[string]string) - for _, ext := range extensions { - val, err := evalString(ext.prog, activation) - if err != nil { - return fmt.Errorf("program: %w", err) - } - extensionsKV[ext.name] = val +func iterate(ctx context.Context, cancel context.CancelFunc, appCtx appCtx, tickDuration time.Duration, stream pbbstream.BlockStreamV2_BlocksClient, out chan error) error { + // loop: receive block, transform block, send message... + zlog.Info("Start looping over blocks...") - } + in := make(chan BlockStep, 10) + closeIn := func() { + zlog.Info("close block input channel") + close(in) + } + defer closeIn() - eventKeys, err := evalStringArray(eventKeyProg, activation) - if err != nil { - return fmt.Errorf("event keyeval: %w", err) - } + ticker := time.NewTicker(tickDuration) + closeTicker := func() { + zlog.Info("stop ticker") + ticker.Stop() + } + defer closeTicker() - dedupeMap := make(map[string]bool) - for _, eventKey := range eventKeys { - if dedupeMap[eventKey] { - continue - } - dedupeMap[eventKey] = true - - headers := []kafka.Header{ - kafka.Header{ - Key: "ce_id", - Value: hashString(fmt.Sprintf("%s%s%d%s%s", blk.Id, trx.Id, act.ExecutionIndex, msg.Step.String(), eventKey)), - }, - sourceHeader, - specHeader, - kafka.Header{ - Key: "ce_type", - Value: []byte(eventType), - }, - contentTypeHeader, - kafka.Header{ - Key: "ce_time", - Value: []byte(blk.MustTime().Format("2006-01-02T15:04:05.9Z")), - }, - dataContentTypeHeader, - { - Key: "ce_blkstep", - Value: []byte(step), - }, - } - for k, v := range extensionsKV { - headers = append(headers, kafka.Header{ - Key: k, - Value: []byte(v), - }) - } - msg := kafka.Message{ - Key: []byte(eventKey), - Headers: headers, - Value: eosioAction.JSON(), - TopicPartition: kafka.TopicPartition{ - Topic: &a.config.KafkaTopic, - Partition: kafka.PartitionAny, - }, - } - if err := s.Send(&msg); err != nil { - return fmt.Errorf("sending message: %w", err) - } - messagesSent.Inc() + go blockHandler(ctx, appCtx, in, ticker.C, out) + for { + select { + case err, ok := <-out: + if !ok { + zlog.Info("error channel has been closed exit 'iterate' goroutine") + return nil + } + zlog.Error("exit block streaming on error", zap.Error(err)) + return err + default: + msg, err := stream.Recv() + if err != nil { + if err == io.EOF { + return nil } - + return fmt.Errorf("error on receive: %w", err) } + blk := &pbcodec.Block{} + if err := ptypes.UnmarshalAny(msg.Block, blk); err != nil { + return fmt.Errorf("decoding any of type %q: %w", msg.Block.TypeUrl, err) + } + zlog.Debug("Receive new block", zap.Uint32("block_num", blk.Number), zap.String("block_id", blk.Id), zap.String("cursor", msg.Cursor)) + blocksReceived.Inc() + blkStep := BlockStep{ + blk: blk, + step: msg.Step, + cursor: msg.Cursor, + } + in <- blkStep } - if a.IsTerminating() { - return s.Commit(context.Background(), msg.Cursor) - } + } +} - if err := s.CommitIfAfter(context.Background(), msg.Cursor, a.config.CommitMinDelay); err != nil { - return fmt.Errorf("committing message: %w", err) +func blockHandler(ctx context.Context, appCtx appCtx, in <-chan BlockStep, ticks <-chan time.Time, out chan<- error) { + var lastBlkStep BlockStep = BlockStep{cursor: appCtx.cursor} + hasFail := false + var adapter Adapter = appCtx.adapter + var s Sender = appCtx.sender + for { + select { + case blkStep, ok := <-in: + if !ok { + zlog.Info("incoming block channel is closed exit 'blockHandler' goroutine") + return + } + if hasFail { + zlog.Debug("skip incoming block message after failure") + continue + } + blkStep.previousCursor = lastBlkStep.cursor + kafkaMsgs, err := adapter.Adapt(blkStep) + if err != nil { + hasFail = true + zlog.Debug("fail fast on adapter.Adapt() send message to -> out chan", zap.Error(err)) + out <- fmt.Errorf("transform to kafka message at block_num: %d, cursor: %s, , %w", blkStep.blk.Number, blkStep.cursor, err) + } + lastBlkStep = blkStep + if len(kafkaMsgs) == 0 { + continue + } + if err = s.Send(ctx, kafkaMsgs, blkStep); err != nil { + hasFail = true + zlog.Debug("fail fast on sender.Send() send message to -> out chan", zap.Error(err)) + out <- fmt.Errorf("send to kafka message at: %s, %w", blkStep.cursor, err) + } + messagesSent.Add(float64(len(kafkaMsgs))) + case _, ok := <-ticks: + if !ok { + zlog.Info("ticker channel is closed exit 'blockHandler' goroutine") + return + } + if hasFail { + zlog.Debug("skip incoming tick message after failure") + continue + } + if err := s.SaveCP(ctx, lastBlkStep); err != nil { + hasFail = true + zlog.Debug("fail fast on sender.SaveCP() send message to -> out chan", zap.Error(err)) + out <- fmt.Errorf("fail to save check point: %s, %w", lastBlkStep.cursor, err) + } } } } +func addAllABIFilter(filter string) string { + return fmt.Sprintf("%s || (action==\"setabi\" && account==\"eosio\")", filter) +} + +func addAccountABIFilter(filter string, account string) string { + return fmt.Sprintf("%s || (action==\"setabi\" && account==\"eosio\" && data.account==\"%s\")", filter, account) +} + +func addExecutedFilter(filter string, executed bool) string { + if filter != "" && executed { + filter = fmt.Sprintf(" && %s", filter) + } + if executed { + filter = fmt.Sprintf("executed%s", filter) + } + return filter +} + func createKafkaConfig(appConf *Config) kafka.ConfigMap { conf := kafka.ConfigMap{ "bootstrap.servers": appConf.KafkaEndpoints, @@ -410,7 +762,131 @@ func createKafkaConfig(appConf *Config) kafka.ConfigMap { if appConf.KafkaSSLAuth { conf["ssl.certificate.location"] = appConf.KafkaSSLClientCertFile conf["ssl.key.location"] = appConf.KafkaSSLClientKeyFile - //conf["ssl.key.password"] = "keypass" } return conf } + +func createKafkaConfigForMessageProducer(appConf *Config) kafka.ConfigMap { + conf := createKafkaConfig(appConf) + compressionType := appConf.KafkaCompressionType + conf["compression.type"] = compressionType + conf["compression.level"] = getCompressionLevel(compressionType, appConf) + conf["message.max.bytes"] = appConf.KafkaMessageMaxBytes + return conf +} + +// CompressionLevel defines the min and max values +type CompressionLevel struct { + Min, Max int +} + +// see documentation https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md +var COMPRESSIONS = map[string]CompressionLevel{ + "none": {0, 0}, + "gzip": {0, 9}, + "snappy": {0, 0}, + "lz4": {0, 12}, + "zstd": {-1, -1}, +} + +func (level CompressionLevel) normalize(value int) int { + if value > level.Max { + zlog.Warn("Invalid compression cannot be more than 12", zap.Int("current", value), zap.Int("max", level.Max)) + return level.Max + } + if value < level.Min { + zlog.Warn("Invalid compression cannot be less than -1", zap.Int("current", value), zap.Int("min", level.Min)) + return level.Min + } + return value +} + +func getCompressionLevel(compressionType string, config *Config) int { + compressionLevel := config.KafkaCompressionLevel + if compressionLevel == -1 { + return compressionLevel + } + level, ok := COMPRESSIONS[compressionType] + if !ok { + return -1 + } + return level.normalize(compressionLevel) +} + +func (c *Config) newABICodec(abiDecoder *ABIDecoder, getSchema MessageSchemaSupplier, construct StreamAbiCodecConstructor) (ABICodec, error) { + switch c.Codec { + case JsonCodec: + return NewJsonABICodec(abiDecoder, c.Account), nil + case AvroCodec: + schemaRegistryClient := srclient.CreateSchemaRegistryClient(c.SchemaRegistryURL) + compatibility, err := c.getCompatibility() + if err != nil { + return nil, fmt.Errorf("getting compatibility level: %w", err) + } + return construct(&DfuseAbiRepository{ + overrides: abiDecoder.overrides, + abiCodecCli: abiDecoder.abiCodecCli, + context: abiDecoder.context, + }, getSchema, schemaRegistryClient, c.Account, c.SchemaRegistryURL, compatibility), nil + default: + return nil, fmt.Errorf("unsupported codec type: '%s'", c.Codec) + } +} + +type MessageSchemaGenerator struct { + Namespace string + MajorVersion uint + Version string + Account string +} + +func (msg MessageSchemaGenerator) getTableSchema(tableName string, abi *ABI) (MessageSchema, error) { + return GenerateTableSchema(msg.newNamedSchemaGenOptions(TABLES_CDC_TYPE, tableName, abi)) +} + +func (msg MessageSchemaGenerator) getActionSchema(actionName string, abi *ABI) (MessageSchema, error) { + return GenerateActionSchema(msg.newNamedSchemaGenOptions(ACTIONS_CDC_TYPE, actionName, abi)) +} + +func (msg MessageSchemaGenerator) getNoopSchema(name string, _ *ABI) (MessageSchema, error) { + return MessageSchema{}, fmt.Errorf("noop schema generator cannot produce schema for: %s", name) +} + +func (msg MessageSchemaGenerator) newNamedSchemaGenOptions(kind string, name string, abi *ABI) NamedSchemaGenOptions { + return NamedSchemaGenOptions{ + Name: name, + Namespace: msg.namespace(kind, abi.Account), + Version: schemaVersion(msg.Version, msg.MajorVersion, abi.AbiBlockNum), + AbiSpec: abi, + Domain: abi.Account, + } +} + +func (msg MessageSchemaGenerator) namespace(kind string, account string) string { + accountLowerNameSpace := strings.ToLower(account) + // Check if the first character is a number, prepend '_' if so + if len(accountLowerNameSpace) > 0 && accountLowerNameSpace[0] >= '0' && accountLowerNameSpace[0] <= '9' { + accountLowerNameSpace = "_" + accountLowerNameSpace + } + var result string + if msg.Namespace == "" { + result = fmt.Sprintf("%s.%s.v%d", accountLowerNameSpace, kind, msg.MajorVersion) + } else { + result = fmt.Sprintf("%s.%s.%s.v%d", msg.Namespace, accountLowerNameSpace, kind, msg.MajorVersion) + } + return result +} + +func schemaVersion(version string, majorVersion uint, abiBlockNumber uint32) string { + if version == "" { + if majorVersion == 0 { + zlog.Warn("Using abiBlockNumber for minor version, but majorVersion wasn't specified.") + } + return fmt.Sprintf("%d.%d.0", majorVersion, abiBlockNumber) + } else { + if majorVersion != 0 { + zlog.Warn("Major version is defined, but being ignored, since version is also defined and takes precedence.") + } + return version + } +} diff --git a/app_test.go b/app_test.go new file mode 100644 index 0000000..d2c2ba0 --- /dev/null +++ b/app_test.go @@ -0,0 +1,581 @@ +package dkafka + +import ( + "reflect" + "testing" + + pbcodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/codec/v1" +) + +func Test_getCompressionLevel(t *testing.T) { + type args struct { + compressionType string + config *Config + } + tests := []struct { + name string + args args + want int + }{ + {"default", args{"snappy", &Config{KafkaCompressionLevel: -1}}, -1}, + {"gzip normal", args{"gzip", &Config{KafkaCompressionLevel: 2}}, 2}, + {"gzip outer up", args{"gzip", &Config{KafkaCompressionLevel: 12}}, 9}, + {"gzip outer down", args{"gzip", &Config{KafkaCompressionLevel: -2}}, 0}, + {"snappy normal", args{"snappy", &Config{KafkaCompressionLevel: 0}}, 0}, + {"snappy outer up", args{"snappy", &Config{KafkaCompressionLevel: 12}}, 0}, + {"snappy outer down", args{"snappy", &Config{KafkaCompressionLevel: -2}}, 0}, + {"lz4 normal", args{"lz4", &Config{KafkaCompressionLevel: 12}}, 12}, + {"lz4 outer up", args{"lz4", &Config{KafkaCompressionLevel: 143}}, 12}, + {"lz4 outer down", args{"lz4", &Config{KafkaCompressionLevel: -12}}, 0}, + {"zstd normal", args{"zstd", &Config{KafkaCompressionLevel: 2}}, -1}, + {"zstd outer up", args{"zstd", &Config{KafkaCompressionLevel: 12}}, -1}, + {"zstd outer down", args{"zstd", &Config{KafkaCompressionLevel: -2}}, -1}, + {"unknown", args{"????", &Config{KafkaCompressionLevel: 4}}, -1}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getCompressionLevel(tt.args.compressionType, tt.args.config); got != tt.want { + t.Errorf("getCompressionLevel() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getCorrelation(t *testing.T) { + type args struct { + Actions []*pbcodec.ActionTrace + } + tests := []struct { + Name string + Args args + WantCorrelation *Correlation + WantErr bool + }{ + {"empty", args{}, nil, false}, + {"not-empty-no-correlations", args{[]*pbcodec.ActionTrace{{Action: &pbcodec.Action{Account: "eosio.token", Name: "transfer"}}}}, nil, false}, + {"not-empty-with-correlations-first", args{[]*pbcodec.ActionTrace{{Action: &pbcodec.Action{Account: "ultra.tools", Name: "correlate", JsonData: `{"payer":"ultra", "correlation_id":"123"}`}}, {Action: &pbcodec.Action{Account: "eosio.token", Name: "transfer"}}}}, &Correlation{"ultra", "123"}, false}, + {"missing-correlations-json-data", args{[]*pbcodec.ActionTrace{{Action: &pbcodec.Action{Account: "ultra.tools", Name: "correlate"}}}}, nil, true}, + {"not-empty-with-correlations-second", args{[]*pbcodec.ActionTrace{{Action: &pbcodec.Action{Account: "eosio.token", Name: "transfer"}}, {Action: &pbcodec.Action{Account: "ultra.tools", Name: "correlate", JsonData: `{"payer":"ultra", "correlation_id":"123"}`}}}}, &Correlation{"ultra", "123"}, false}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + gotCorrelation, err := getCorrelation(tt.Args.Actions) + if (err != nil) != tt.WantErr { + t.Errorf("getCorrelation() error = %v, wantErr %v", err, tt.WantErr) + return + } + if !reflect.DeepEqual(gotCorrelation, tt.WantCorrelation) { + t.Errorf("getCorrelation() = %v, want %v", gotCorrelation, tt.WantCorrelation) + } + }) + } +} + +func Test_buildTableKeyExtractorFinder(t *testing.T) { + dbOp := &pbcodec.DBOp{ + Scope: "vince", + PrimaryKey: "42", + } + type finderCheck struct { + tableName string + dbOp *pbcodec.DBOp + wantKey string + } + tests := []struct { + name string + args []string + want []finderCheck + wantErr bool + }{ + { + name: "too many elements", + args: []string{"vincent:ana:mark"}, + wantErr: true, + }, + { + name: "bad key mapping option", + args: []string{"account:?"}, + wantErr: true, + }, + { + name: "primary key extractor", + args: []string{"account:k"}, + want: []finderCheck{{"account", dbOp, "42"}}, + }, + { + name: "full key extractor", + args: []string{"account:s+k"}, + want: []finderCheck{{"account", dbOp, "vince:42"}}, + }, + { + name: "scope key extractor", + args: []string{"account:s"}, + want: []finderCheck{{"account", dbOp, "vince"}}, + }, + { + name: "default key extractor (primaryKey)", + args: []string{"account"}, + want: []finderCheck{{"account", dbOp, "vince:42"}}, + }, + { + name: "multi tables", + args: []string{"account:k", "token:s+k"}, + want: []finderCheck{{"account", dbOp, "42"}, {"token", dbOp, "vince:42"}}, + }, + { + name: "wildcard table filter with default key mapping", + args: []string{"*"}, + want: []finderCheck{{"account", dbOp, "vince:42"}, {"token", dbOp, "vince:42"}}, + }, + { + name: "wildcard table filter with specific primary key mapping", + args: []string{"*:k"}, + want: []finderCheck{{"account", dbOp, "42"}, {"token", dbOp, "42"}}, + }, + { + name: "wildcard table filter with specific full key mapping", + args: []string{"*:s+k"}, + want: []finderCheck{{"account", dbOp, "vince:42"}, {"token", dbOp, "vince:42"}}, + }, + { + name: "mix of wildcard table filter and specific name table filter", + args: []string{"*:s+k", "account:k"}, + want: []finderCheck{{"account", dbOp, "42"}, {"token", dbOp, "vince:42"}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotFinder, err := buildTableKeyExtractorFinder(tt.args) + if (err != nil) != tt.wantErr { + t.Errorf("buildTableKeyExtractorFinder(tablesConfig) error = %v, wantErr %v", err, tt.wantErr) + return + } + + for _, finderCheck := range tt.want { + keyExtractorFunc, found := gotFinder(finderCheck.tableName) + if !found { + t.Errorf("TableKeyExtractorFinder(tableName) not found for table= %s", finderCheck.tableName) + } + if key := keyExtractorFunc(finderCheck.dbOp); key != finderCheck.wantKey { + t.Errorf("(table=%s) ExtractKey(dbOp)= %s, want %s", finderCheck.tableName, key, finderCheck.wantKey) + } + } + }) + } +} + +func Test_createCdcKeyExpressions(t *testing.T) { + tests := []struct { + name string + cdcExpression string + knownActions []string + unknownAction string + wantErr bool + }{ + { + name: "invalid-expression", + cdcExpression: "test", + wantErr: true, + }, + { + name: "single", + cdcExpression: "{\"create\": \"transaction_id\"}", + knownActions: []string{"create"}, + unknownAction: "issue", + }, + { + name: "multi", + cdcExpression: "{\"create\": \"transaction_id\", \"buy\": \"transaction_id\"}", + knownActions: []string{"create", "buy"}, + unknownAction: "issue", + }, + { + name: "only-wildcard", + cdcExpression: "{\"*\": \"first_auth_actor\"}", + knownActions: []string{"create", "buy"}, + }, + { + name: "action-and-wildcard", + cdcExpression: "{\"create\": \"transaction_id\", \"*\": \"first_auth_actor\"}", + knownActions: []string{"create", "buy", "any"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotFinder, err := createCdcKeyExpressions(tt.cdcExpression) + if (err != nil) != tt.wantErr { + t.Errorf("createCdcKeyExpressions() error = %v, wantErr %v", err, tt.wantErr) + return + } else if !tt.wantErr { + for _, knownAction := range tt.knownActions { + if _, found := gotFinder(knownAction); !found { + t.Errorf("createCdcKeyExpressions(...)(%s) not found", knownAction) + return + } + } + if _, found := gotFinder(tt.unknownAction); tt.unknownAction != "" && found { + t.Errorf("createCdcKeyExpressions(...)(%s) found while should be unknown", tt.unknownAction) + return + } + } + }) + } +} + +func Test_addExecutedFilter(t *testing.T) { + type args struct { + filter string + executed bool + } + tests := []struct { + name string + args args + want string + }{ + { + name: "executed with filter", + args: args{ + filter: "filter", + executed: true, + }, + want: "executed && filter", + }, + { + name: "executed without filter", + args: args{ + filter: "", + executed: true, + }, + want: "executed", + }, + { + name: "not executed without filter", + args: args{ + filter: "", + executed: false, + }, + want: "", + }, + { + name: "not executed with filter", + args: args{ + filter: "filter", + executed: false, + }, + want: "filter", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := addExecutedFilter(tt.args.filter, tt.args.executed); got != tt.want { + t.Errorf("addExecutedFilter() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMessageSchemaGenerator_namespace(t *testing.T) { + type fields struct { + Namespace string + MajorVersion uint + Version string + Account string + } + type args struct { + kind string + account string + } + tests := []struct { + name string + fields fields + args args + want string + }{ + // eosio.eba + { + name: "eosio.eba-actions", + fields: fields{ + Namespace: "io.dkafka.data", + MajorVersion: 1, + Version: "1.2.3", + Account: "eosio.eba", + }, + args: args{ + kind: ACTIONS_CDC_TYPE, + account: "eosio.eba", + }, + want: "io.dkafka.data.eosio.eba.actions.v1", + }, + { + name: "eosio.eba-tables", + fields: fields{ + Namespace: "io.dkafka.data", + MajorVersion: 1, + Version: "1.2.3", + Account: "eosio.eba", + }, + args: args{ + kind: TABLES_CDC_TYPE, + account: "eosio.eba", + }, + want: "io.dkafka.data.eosio.eba.tables.v1", + }, + // eosio.evm + { + name: "eosio.evm-actions", + fields: fields{ + Namespace: "io.dkafka.data", + MajorVersion: 1, + Version: "1.2.3", + Account: "eosio.evm", + }, + args: args{ + kind: ACTIONS_CDC_TYPE, + account: "eosio.evm", + }, + want: "io.dkafka.data.eosio.evm.actions.v1", + }, + { + name: "eosio.evm-tables", + fields: fields{ + Namespace: "io.dkafka.data", + MajorVersion: 1, + Version: "1.2.3", + Account: "eosio.evm", + }, + args: args{ + kind: TABLES_CDC_TYPE, + account: "eosio.evm", + }, + want: "io.dkafka.data.eosio.evm.tables.v1", + }, + // eosio.group + { + name: "eosio.group-actions", + fields: fields{ + Namespace: "io.dkafka.data", + MajorVersion: 1, + Version: "1.2.3", + Account: "eosio.group", + }, + args: args{ + kind: ACTIONS_CDC_TYPE, + account: "eosio.group", + }, + want: "io.dkafka.data.eosio.group.actions.v1", + }, + { + name: "eosio.group-tables", + fields: fields{ + Namespace: "io.dkafka.data", + MajorVersion: 1, + Version: "1.2.3", + Account: "eosio.group", + }, + args: args{ + kind: TABLES_CDC_TYPE, + account: "eosio.group", + }, + want: "io.dkafka.data.eosio.group.tables.v1", + }, + // eosio.nft.ft + { + name: "eosio.nft.ft-actions", + fields: fields{ + Namespace: "io.dkafka.data", + MajorVersion: 2, + Version: "1.2.3", + Account: "eosio.nft.ft", + }, + args: args{ + kind: ACTIONS_CDC_TYPE, + account: "eosio.nft.ft", + }, + want: "io.dkafka.data.eosio.nft.ft.actions.v2", + }, + { + name: "eosio.nft.ft-tables", + fields: fields{ + Namespace: "io.dkafka.data", + MajorVersion: 2, + Version: "1.2.3", + Account: "eosio.nft.ft", + }, + args: args{ + kind: TABLES_CDC_TYPE, + account: "eosio.nft.ft", + }, + want: "io.dkafka.data.eosio.nft.ft.tables.v2", + }, + // eosio.token + { + name: "eosio.token-actions", + fields: fields{ + Namespace: "io.dkafka.data", + MajorVersion: 2, + Version: "1.2.3", + Account: "eosio.token", + }, + args: args{ + kind: ACTIONS_CDC_TYPE, + account: "eosio.token", + }, + want: "io.dkafka.data.eosio.token.actions.v2", + }, + { + name: "eosio.token-tables", + fields: fields{ + Namespace: "io.dkafka.data", + MajorVersion: 2, + Version: "1.2.3", + Account: "eosio.token", + }, + args: args{ + kind: TABLES_CDC_TYPE, + account: "eosio.token", + }, + want: "io.dkafka.data.eosio.token.tables.v2", + }, + // ultra.claim + { + name: "ultra.claim-actions", + fields: fields{ + Namespace: "io.dkafka.data", + MajorVersion: 1, + Version: "1.2.3", + Account: "ultra.claim", + }, + args: args{ + kind: ACTIONS_CDC_TYPE, + account: "ultra.claim", + }, + want: "io.dkafka.data.ultra.claim.actions.v1", + }, + { + name: "ultra.claim-tables", + fields: fields{ + Namespace: "io.dkafka.data", + MajorVersion: 1, + Version: "1.2.3", + Account: "ultra.claim", + }, + args: args{ + kind: TABLES_CDC_TYPE, + account: "ultra.claim", + }, + want: "io.dkafka.data.ultra.claim.tables.v1", + }, + // ultra.rgrab + { + name: "ultra.rgrab-actions", + fields: fields{ + Namespace: "io.dkafka.data", + MajorVersion: 1, + Version: "1.2.3", + Account: "ultra.rgrab", + }, + args: args{ + kind: ACTIONS_CDC_TYPE, + account: "ultra.rgrab", + }, + want: "io.dkafka.data.ultra.rgrab.actions.v1", + }, + { + name: "ultra.rgrab-tables", + fields: fields{ + Namespace: "io.dkafka.data", + MajorVersion: 1, + Version: "1.2.3", + Account: "ultra.rgrab", + }, + args: args{ + kind: TABLES_CDC_TYPE, + account: "ultra.rgrab", + }, + want: "io.dkafka.data.ultra.rgrab.tables.v1", + }, + { + name: "empty-namespace-actions", + fields: fields{ + Namespace: "", + MajorVersion: 1, + Version: "1.2.3", + Account: "ultra.rgrab", + }, + args: args{ + kind: ACTIONS_CDC_TYPE, + account: "ultra.rgrab", + }, + want: "ultra.rgrab.actions.v1", + }, + { + name: "tables", + fields: fields{ + Namespace: "io.dkafka.data", + MajorVersion: 1, + Version: "1.2.3", + Account: "ultra.rgrab", + }, + args: args{ + kind: TABLES_CDC_TYPE, + account: "ultra.rgrab", + }, + want: "io.dkafka.data.ultra.rgrab.tables.v1", + }, + { + name: "empty-namespace-tables", + fields: fields{ + Namespace: "", + MajorVersion: 1, + Version: "1.2.3", + Account: "ultra.rgrab", + }, + args: args{ + kind: TABLES_CDC_TYPE, + account: "ultra.rgrab", + }, + want: "ultra.rgrab.tables.v1", + }, + { + name: "eba-account-tables", + fields: fields{ + Namespace: "io.dkafka.data", + MajorVersion: 1, + Version: "1.2.3", + Account: "aa1aa2aa3aa4", + }, + args: args{ + kind: TABLES_CDC_TYPE, + account: "aa1aa2aa3aa4", + }, + want: "io.dkafka.data.aa1aa2aa3aa4.tables.v1", + }, + { + name: "non-eba-account-tables", + fields: fields{ + Namespace: "io.dkafka.data", + MajorVersion: 1, + Version: "1.2.3", + Account: "1aa2aa3aa4bx", + }, + args: args{ + kind: TABLES_CDC_TYPE, + account: "1aa2aa3aa4bx", + }, + want: "io.dkafka.data._1aa2aa3aa4bx.tables.v1", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + msg := MessageSchemaGenerator{ + Namespace: tt.fields.Namespace, + MajorVersion: tt.fields.MajorVersion, + Version: tt.fields.Version, + Account: tt.fields.Account, + } + if got := msg.namespace(tt.args.kind, tt.args.account); got != tt.want { + t.Errorf("MessageSchemaGenerator.namespace() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/avro-tools-1.12.0.jar.sha512 b/avro-tools-1.12.0.jar.sha512 new file mode 100644 index 0000000..0184383 --- /dev/null +++ b/avro-tools-1.12.0.jar.sha512 @@ -0,0 +1 @@ +f626d59b91d8a7e7d75ba39818e84e7734775e87ddc2c58b1608b2850453e4a5e921afa228bae69dfb2e0a52466365926425d4b057a61415faae7318fe3c41cf avro-tools-1.12.0.jar \ No newline at end of file diff --git a/avro.go b/avro.go new file mode 100644 index 0000000..dc94df5 --- /dev/null +++ b/avro.go @@ -0,0 +1,241 @@ +package dkafka + +import ( + "encoding/json" + "fmt" + "regexp" + + "github.com/iancoleman/strcase" +) + +var namePattern *regexp.Regexp +var namespacePattern *regexp.Regexp + +func init() { + namePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) + namespacePattern = regexp.MustCompile(`^([A-Za-z_][A-Za-z0-9_]*)?(?:\.[A-Za-z_][A-Za-z0-9_]*)*$`) +} + +func checkName(name string) (string, error) { + if namePattern.MatchString(name) { + return name, nil + } else { + return name, fmt.Errorf("invalid Avro name: %s", name) + } +} + +func checkNamespace(np string) (string, error) { + if namespacePattern.MatchString(np) { + return np, nil + } else { + return np, fmt.Errorf("invalid Avro namespace: %s", np) + } +} + +// Schema is represented in JSON by one of: +// - A JSON string, naming a defined type. +// - A JSON object, of the form: +// - {"type": "typeName" ...attributes...} +// where typeName is either a primitive or derived type name, as defined below. Attributes not defined in this document are permitted as metadata, but must not affect the format of serialized data. +// A JSON array, representing a union of embedded types. +type Schema = interface{} + +type TypedSchema struct { + EosType string `json:"eos.type,omitempty"` + LogicalType string `json:"logicalType,omitempty"` + Type string `json:"type"` + Convert string `json:"convert,omitempty"` +} + +type MetaSupplier interface { + GetVersion() string + GetDomain() string + GetCompatibility() string + GetType() string +} + +type MetaSchema struct { + Compatibility string `json:"compatibility"` + Type string `json:"type"` + Version string `json:"version,omitempty"` + Domain string `json:"domain,omitempty"` +} + +type MessageSchema struct { + RecordSchema + Meta MetaSchema `json:"meta"` +} + +type FieldSchema struct { + // Name a JSON string providing the name of the field (required) + Name string `json:"name"` + // Doc a JSON string describing this field for users (optional). + Doc string `json:"doc,omitempty"` + // Type a schema, as defined above + Type Schema `json:"type"` + // A default value for this field, only used when reading instances that lack the field for schema evolution purposes. + Default json.RawMessage `json:"default,omitempty"` +} + +type RecordSchema struct { + // type always equal to "record" + Type string `json:"type"` + // Name a JSON string providing the name of the record (required) + Name string `json:"name"` + // Namespace a JSON string that qualifies the name + Namespace string `json:"namespace,omitempty"` + // Doc a JSON string providing documentation to the user of this schema (optional). + Doc string `json:"doc,omitempty"` + // Convert a native type to a goavro + // map[string]interface{} that follow this specification/format. + // It's the name of the function that do the convertion and is + // available in the goavro schema builder context. + Convert string `json:"convert,omitempty"` + // Fields a JSON array, listing fields (required). Each field is a JSON object. + Fields []FieldSchema `json:"fields"` +} + +func (r *RecordSchema) AsCodecId() CodecId { + return CodecId{ + Account: r.Namespace, + Name: r.Name, + } +} + +func newRecordS(name string, fields []FieldSchema) RecordSchema { + return newRecordFQN("", name, fields) +} + +func newRecordFQN(np string, name string, fields []FieldSchema) RecordSchema { + return RecordSchema{ + Type: "record", + Name: strcase.ToCamel(name), + Namespace: np, + Fields: fields, + } +} + +func newMeta(supplier MetaSupplier) MetaSchema { + return MetaSchema{ + Compatibility: "FORWARD", + Type: "notification", + Version: supplier.GetVersion(), + Domain: supplier.GetDomain(), + } +} + +type ArraySchema struct { + // type always equal to "array" + Type string `json:"type"` + // items the schema of the array's items. + Items Schema `json:"items"` + // todo manage default +} + +type Union = []Schema + +func NewArray(itemType Schema) ArraySchema { + return ArraySchema{ + Type: "array", + Items: itemType, + } +} + +var _defaultNull = json.RawMessage("null") + +func NewNullableField(n string, t Schema) FieldSchema { + return FieldSchema{ + Name: n, + Type: t, + Default: _defaultNull, + } +} + +func NewOptionalField(n string, t Schema) FieldSchema { + return NewNullableField(n, NewOptional(t)) +} + +func NewOptional(schema Schema) Union { + switch typpedSchema := schema.(type) { + case []interface{}: + return append([]interface{}{"null"}, typpedSchema...) + default: + return []Schema{"null", schema} + } +} + +func NewTimestampMillisField(name string) FieldSchema { + return FieldSchema{ + Name: name, + Type: NewTimestampMillisType("block_timestamp_type"), + } +} + +func NewTimestampMillisType(eosType string) TypedSchema { + return TypedSchema{ + Type: "long", + LogicalType: "timestamp-millis", + EosType: eosType, + } +} + +type DecimalLogicalType struct { + TypedSchema + + Precision int `json:"precision"` + Scale int `json:"scale"` + Convert string `json:"convert,omitempty"` +} + +func NewInt128Type() DecimalLogicalType { + return DecimalLogicalType{ + TypedSchema: TypedSchema{ + Type: "bytes", + LogicalType: "decimal", + EosType: "int128", + }, + Scale: 0, + Precision: 39, + Convert: "eos.Int128", + } +} + +func NewUint64Type() DecimalLogicalType { + return DecimalLogicalType{ + TypedSchema: TypedSchema{ + Type: "bytes", + LogicalType: "decimal", + EosType: "uint64", + }, + Precision: 20, + Scale: 0, + } +} + +func NewUint128Type() DecimalLogicalType { + return DecimalLogicalType{ + TypedSchema: TypedSchema{ + Type: "bytes", + LogicalType: "decimal", + EosType: "uint128", + }, + Precision: 39, + Scale: 0, + Convert: "eos.Uint128", + } +} + +func NewIntField(name string) FieldSchema { + return FieldSchema{ + Name: name, + Type: "int", + } +} + +func NewSymbolType() TypedSchema { + return TypedSchema{ + Type: "string", + Convert: "eos.Symbol", + EosType: "symbol", + } +} diff --git a/avro_test.go b/avro_test.go new file mode 100644 index 0000000..3c05227 --- /dev/null +++ b/avro_test.go @@ -0,0 +1,95 @@ +package dkafka + +import ( + "testing" +) + +func Test_checkName(t *testing.T) { + type args struct { + name string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + "valid", + args{ + "ValidName", + }, + "ValidName", + false, + }, + { + "invalid", + args{ + "$ValidName", + }, + "$ValidName", + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := checkName(tt.args.name) + if (err != nil) != tt.wantErr { + t.Errorf("checkName() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("checkName() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_checkNamespace(t *testing.T) { + type args struct { + np string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + "empty", + args{ + "", + }, + "", + false, + }, + { + "valid", + args{ + "io.dkafka", + }, + "io.dkafka", + false, + }, + { + "invalid", + args{ + "io::dkafka", + }, + "io::dkafka", + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := checkNamespace(tt.args.np) + if (err != nil) != tt.wantErr { + t.Errorf("checkNamespace() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("checkNamespace() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/brequest.go b/brequest.go new file mode 100644 index 0000000..4b95e2d --- /dev/null +++ b/brequest.go @@ -0,0 +1,175 @@ +package dkafka + +import ( + "fmt" + + "github.com/confluentinc/confluent-kafka-go/kafka" + "github.com/streamingfast/bstream/forkable" + pbbstream "github.com/streamingfast/pbgo/dfuse/bstream/v1" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type position struct { + cursor *forkable.Cursor + opaqueCursor string + previousCursor *forkable.Cursor + previousOpaqueCursor string +} + +func (p position) gt(that position) bool { + if p.previousCursor != nil && that.previousCursor == nil { + return true + } + if p.previousCursor == nil && that.previousCursor != nil { + return false + } + if p.previousCursor != nil && that.previousCursor != nil { + return that.previousCursor.Block.Num() < p.previousCursor.Block.Num() + } + if p.cursor == nil { + return false + } + if that.cursor == nil { + return true + } + return that.cursor.Block.Num() < p.cursor.Block.Num() +} + +func (p position) opaque() string { + if len(p.previousOpaqueCursor) > 0 { + return p.previousOpaqueCursor + } else { + return p.opaqueCursor + } +} + +func NewRequest(filter string, startBlock int64, stopBlock uint64, cursor string, irreversibleOnly bool) *pbbstream.BlocksRequestV2 { + req := pbbstream.BlocksRequestV2{ + IncludeFilterExpr: filter, + StartBlockNum: startBlock, + StopBlockNum: stopBlock, + StartCursor: cursor, + } + if irreversibleOnly { + zlog.Debug("Request only irreversible blocks") + req.ForkSteps = []pbbstream.ForkStep{pbbstream.ForkStep_STEP_IRREVERSIBLE} + } + return &req +} + +func findPosition(headers []kafka.Header) (position position) { + for _, header := range headers { + zlog.Debug("check heard", zap.String("key", header.Key), zap.ByteString("value", header.Value)) + if header.Key == CursorHeaderKey { + zlog.Debug("find cursor", zap.String("key", header.Key), zap.ByteString("value", header.Value)) + position.opaqueCursor = string(header.Value) + } + if header.Key == PreviousCursorHeaderKey { + zlog.Debug("find cursor", zap.String("key", header.Key), zap.ByteString("value", header.Value)) + position.previousOpaqueCursor = string(header.Value) + } + } + return +} + +// LoadCursor load the latest cursor stored in the given topic and update the request accordingly +func LoadCursor(config kafka.ConfigMap, topic string) (string, error) { + config["group.id"] = "cursor-loader" + config["enable.auto.commit"] = false + + consumer, err := kafka.NewConsumer(&config) + if err != nil { + return "", fmt.Errorf("creating consumer to load cursor: %w", err) + } + + defer func() { + if err := consumer.Close(); err != nil { + zlog.Error("error closing consumer after loading cursor", zap.Error(err)) + } + }() + + consumer.Subscribe(topic, nil) + + md, err := consumer.GetMetadata(&topic, false, 500) + if err != nil { + return "", fmt.Errorf("getting metadata for loading cursor from topic: %s,error: %w", topic, err) + } + parts := md.Topics[topic].Partitions + if len(parts) == 0 { + zlog.Info("topic does not exist no cursor to load", zap.String("topic", topic)) + return "", nil + } + var latest position = position{} + for _, partition := range parts { + position, err := getHeadCursorFromPartition(consumer, topic, partition) + zlog.Info("compare cursor position to find the latest one", zap.String("topic", topic), zap.Int32("partition", partition.ID), zap.Any("current_position", position), zap.Any("latest_position", latest)) + if err != nil { + return "", err + } + if position.cursor == nil { // happen if strange messages in the topic or old dkafka messages + continue + } + if position.gt(latest) { + zlog.Debug("found max cursor", zap.Int32("partition", partition.ID), zap.Uint64("block_num", position.cursor.Block.Num())) + latest = position + } + } + return latest.opaque(), nil +} + +func getHeadCursorFromPartition(consumer *kafka.Consumer, topic string, partition kafka.PartitionMetadata) (position position, err error) { + low, high, err := consumer.QueryWatermarkOffsets(topic, partition.ID, 500) + if err != nil { + return position, fmt.Errorf("getting low/high watermark for topic: %s, partition: %d, error: %w", topic, partition.ID, err) + } + + for i := kafka.Offset(high) - 1; i >= kafka.Offset(low); i-- { + zlog.Info("retrieve cursor header from kafka message", zap.String("topic", topic), zap.Int32("partition", partition.ID), zap.Int64("offset", int64(i))) + err = consumer.Assign([]kafka.TopicPartition{ + { + Topic: &topic, + Partition: partition.ID, + Offset: i, + }}) + + if err != nil { + zlog.Debug("fail to retrieve cursor header from kafka message", zap.String("topic", topic), zap.Int32("partition", partition.ID), zap.Int64("offset", int64(i)), zap.Error(err)) + return + } + + ev := consumer.Poll(1000) + switch event := ev.(type) { + case kafka.Error: + return position, event + case *kafka.Message: + zlog.Debug("look for cursor header", zap.Int("nb_headers", len(event.Headers))) + position = findPosition(event.Headers) + if position.opaqueCursor == "" { + // should not happen but if the producer in this topic are not only dkafka instances... + // or if the message where produce by a very old version of dkafka + // which was not using cursor headers + zlog.Info("no cursor found in headers", zap.String("topic", topic), zap.Int32("partition", partition.ID), zap.Int64("offset", int64(i))) + continue + } + zlog.Debug("read opaque cursor") + if position.cursor, err = forkable.CursorFromOpaque(position.opaqueCursor); err != nil { + return + } + position.previousCursor, _ = forkable.CursorFromOpaque(position.previousOpaqueCursor) + return + default: + zlog.Info("un-handled kafka.Event type", zap.Any("event", event)) + } + } + return +} + +type kHeaders []kafka.Header + +func (hs kHeaders) MarshalLogArray(enc zapcore.ArrayEncoder) error { + for _, h := range hs { + enc.AppendString(fmt.Sprintf("key:%s, value:%s", h.Key, h.Value)) + } + return nil +} diff --git a/brequest_test.go b/brequest_test.go new file mode 100644 index 0000000..50c4d4c --- /dev/null +++ b/brequest_test.go @@ -0,0 +1,190 @@ +package dkafka + +import ( + "reflect" + "testing" + + "github.com/confluentinc/confluent-kafka-go/kafka" + "github.com/streamingfast/bstream/forkable" +) + +const opaqueCursor1 = "6QXTyGxzkcfhhxXa-8kWF6WzLpc-DFJmUQLgLBFEj4vz9XfM1Z6jBWNxaRuDxqDwjka-TA79jN2bHXspoJZRvYTrlbg25SM-RC8lmt_oqeXncKH3MV8Ydbw3C-KJY9nRUzXTaw_9c7AK4NDiP_rRbxA7Zc90LGLg2z9Y84dUJqIQ6ndnw22rJc_S0P-SoIdE_LF2RO2jliimBzF8eBtTOs-BZ_Kbuzp2MA==" + +var cursor1 = cursorFromOpaque(opaqueCursor1) + +const opaqueCursor2 = "hsDNMretWsuoLJ-5SF69saWzLpc-DFJmUQLgLBFFj4vz9XfM1Z6iBmRxbEmBwvqm2kS6Swup2dvLEHl8o8IEtNPpx-xkvyE9RHopxtru-7Xre6b7PFgbd-lhXu2JZNnRUzXTaw_9c7AK4NDiP_rRbxA7Zc90LGLg2z9Y84dUJqIQ6ndnw22rJc_S0P-SoIdE_LF2RO2jliimBzF8eBtTOs-BZ_Kbuzp2MA==" + +var cursor2 = cursorFromOpaque(opaqueCursor2) + +func cursorFromOpaque(in string) (cursor *forkable.Cursor) { + cursor, _ = forkable.CursorFromOpaque(in) + return +} + +func Test_findPosition(t *testing.T) { + type args struct { + headers []kafka.Header + } + tests := []struct { + name string + args args + wantPosition position + }{ + { + name: "empty", + args: args{[]kafka.Header{}}, + wantPosition: position{}, + }, + { + name: "only cursor", + args: args{[]kafka.Header{{ + Key: CursorHeaderKey, + Value: []byte(opaqueCursor1), + }, + }, + }, + wantPosition: position{ + opaqueCursor: opaqueCursor1, + }, + }, + { + name: "full cursors", + args: args{[]kafka.Header{{ + Key: CursorHeaderKey, + Value: []byte(opaqueCursor1), + }, + { + Key: PreviousCursorHeaderKey, + Value: []byte(opaqueCursor2), + }, + { + Key: CursorHeaderKey, + Value: []byte(opaqueCursor1), + }, + }, + }, + wantPosition: position{ + opaqueCursor: opaqueCursor1, + previousOpaqueCursor: opaqueCursor2, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotPosition := findPosition(tt.args.headers); !reflect.DeepEqual(gotPosition, tt.wantPosition) { + t.Errorf("findPosition() = %v, want %v", gotPosition, tt.wantPosition) + } + }) + } +} + +func Test_position_gt(t *testing.T) { + tests := []struct { + name string + this position + that position + want bool + }{ + { + name: "no previous this greater than that", + this: position{ + cursor: cursor1, + }, + that: position{ + cursor: cursor2, + }, + want: true, + }, + { + name: "no previous this lower than that", + this: position{ + cursor: cursor2, + }, + that: position{ + cursor: cursor1, + }, + want: false, + }, + { + name: "no previous this equals to that", + this: position{ + cursor: cursor1, + }, + that: position{ + cursor: cursor1, + }, + want: false, + }, + { + name: "this with previous and not that", + this: position{ + cursor: cursor1, + previousCursor: cursor2, + }, + that: position{ + cursor: cursor2, + }, + want: true, + }, + { + name: "this without previous but that", + this: position{ + cursor: cursor1, + }, + that: position{ + cursor: cursor2, + previousCursor: cursor2, + }, + want: false, + }, + { + name: "this and that with previous with this gt that", + this: position{ + previousCursor: cursor1, + }, + that: position{ + previousCursor: cursor2, + }, + want: true, + }, + { + name: "this and that with previous with that gt this", + this: position{ + previousCursor: cursor2, + }, + that: position{ + previousCursor: cursor1, + }, + want: false, + }, + { + name: "this and that cursor empty", + this: position{}, + that: position{}, + want: false, + }, + { + name: "this cursor empty and not that", + this: position{}, + that: position{ + cursor: cursor1, + }, + want: false, + }, + { + name: "this cursor not empty but that", + this: position{ + cursor: cursor1, + }, + that: position{}, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.this.gt(tt.that); got != tt.want { + t.Errorf("position.gt() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/cel.go b/cel.go index 1473ac6..c45fba6 100644 --- a/cel.go +++ b/cel.go @@ -1,14 +1,300 @@ package dkafka import ( + "encoding/json" "fmt" + "reflect" + "strings" + "time" - "github.com/dfuse-io/dfuse-eosio/filtering" + pbcodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/codec/v1" "github.com/google/cel-go/cel" + "github.com/google/cel-go/checker/decls" + "github.com/google/cel-go/interpreter" + "go.uber.org/zap" ) +var Declarations = cel.Declarations( + decls.NewVar("receiver", decls.String), // eosio.account_name::receiver + decls.NewVar("account", decls.String), // eosio.account_name::account + decls.NewVar("action", decls.String), // eosio.name::action + + decls.NewVar("block_num", decls.Uint), // uint32 block number + decls.NewVar("block_id", decls.String), // string block id (hash) + decls.NewVar("block_time", decls.String), // string timestamp + + decls.NewVar("step", decls.String), // one of: Irreversible, New, Undo, Redo, Unknown + decls.NewVar("transaction_id", decls.String), // string transaction id (hash) + decls.NewVar("transaction_index", decls.Uint), // uint transaction position inside the block + decls.NewVar("global_seq", decls.Uint), // uint + decls.NewVar("execution_index", decls.Uint), // uint action position inside the transaction + + decls.NewVar("data", decls.NewMapType(decls.String, decls.Any)), + decls.NewVar("auth", decls.NewListType(decls.String)), + + decls.NewVar("db_ops", decls.NewListType(decls.NewMapType(decls.String, decls.Any))), +) + +func NewActivation(stepName string, transaction *pbcodec.TransactionTrace, trace *pbcodec.ActionTrace, decodedDBOps []*decodedDBOp) (interpreter.Activation, error) { + var activationMap = map[string]interface{}{ + "block_num": transaction.BlockNum, + "block_id": transaction.ProducerBlockId, + "block_time": func() interface{} { return transaction.BlockTime.AsTime().UTC().Format(time.RFC3339) }, + "transaction_id": transaction.Id, + "transaction_index": transaction.Index, + "step": stepName, + "global_seq": func() interface{} { + if trace.Receipt != nil { + return trace.Receipt.GlobalSequence + } + return 0 + }, + "execution_index": trace.ExecutionIndex, + "receiver": func() interface{} { + if trace.Receipt != nil { + return trace.Receipt.Receiver + } + return trace.Receiver + }, + "account": trace.Account(), + "action": trace.Name(), + "auth": func() interface{} { + return tokenizeEOSAuthority(trace.Action.Authorization) + }, + "data": func() interface{} { + jsonData := trace.Action.JsonData + if len(jsonData) == 0 || strings.IndexByte(jsonData, '{') == -1 { + return nil + } + + return jsonStringToMap(jsonData) + }, + "db_ops": func() interface{} { + if trace.Receipt == nil { + return nil + } + // maybe we can have a look to https://github.com/mitchellh/mapstructure/blob/master/mapstructure.go + if rawJSON, err := json.Marshal(decodedDBOps); err != nil { + zlog.Error("invalid dpOps", zap.Error(err), zap.Uint64("globalSequence", trace.Receipt.GlobalSequence)) + } else { + return rawJsonToMap(rawJSON) + } + return nil + }, + } + return interpreter.NewActivation(activationMap) +} + +var TableDeclarations = cel.Declarations( + decls.NewVar("receiver", decls.String), // eosio.account_name::receiver + decls.NewVar("account", decls.String), // eosio.account_name::account + decls.NewVar("action", decls.String), // eosio.name::action + + decls.NewVar("block_num", decls.Uint), // uint32 block number + decls.NewVar("block_id", decls.String), // string block id (hash) + + decls.NewVar("step", decls.String), // one of: Irreversible, New, Undo, Redo, Unknown + decls.NewVar("transaction_id", decls.String), // string transaction id (hash) + decls.NewVar("transaction_index", decls.Uint), // uint transaction position inside the block + decls.NewVar("global_seq", decls.Uint), // uint + decls.NewVar("execution_index", decls.Uint), // uint action position inside the transaction + + decls.NewVar("db_op", decls.NewMapType(decls.String, decls.Any)), + decls.NewVar("table", decls.NewMapType(decls.String, decls.Any)), +) + +func NewTableActivation(stepName string, transaction *pbcodec.TransactionTrace, trace *pbcodec.ActionTrace, decodedDBOp *decodedDBOp) (interpreter.Activation, error) { + var activationMap = map[string]interface{}{ + "block_num": transaction.BlockNum, + "block_id": transaction.ProducerBlockId, + "transaction_id": transaction.Id, + "transaction_index": transaction.Index, + "step": stepName, + "global_seq": func() interface{} { + if trace.Receipt != nil { + return trace.Receipt.GlobalSequence + } + return 0 + }, + "execution_index": trace.ExecutionIndex, + "receiver": func() interface{} { + if trace.Receipt != nil { + return trace.Receipt.Receiver + } + return trace.Receiver + }, + "account": trace.Account(), + "action": trace.Name(), + "db_op": func() interface{} { + if trace.Receipt == nil { + return nil + } + // maybe we can have a look to https://github.com/mitchellh/mapstructure/blob/master/mapstructure.go + if rawJSON, err := json.Marshal(decodedDBOp); err != nil { + zlog.Fatal("invalid dpOp", zap.Error(err), zap.Uint64("globalSequence", trace.Receipt.GlobalSequence)) + } else { + return rawJsonToMap(rawJSON) + } + return nil + }, + "table": func() interface{} { + if trace.Receipt == nil { + return nil + } + switch op := decodedDBOp.Operation; op { + case pbcodec.DBOp_OPERATION_INSERT: + if rawJSON, err := json.Marshal(decodedDBOp.NewJSON); err != nil { + zlog.Fatal("invalid dpOp", zap.Error(err), zap.Uint64("globalSequence", trace.Receipt.GlobalSequence)) + } else { + return rawJsonToMap(rawJSON) + } + case pbcodec.DBOp_OPERATION_UPDATE: + if rawJSON, err := json.Marshal(decodedDBOp.NewJSON); err != nil { + zlog.Fatal("invalid dpOp", zap.Error(err), zap.Uint64("globalSequence", trace.Receipt.GlobalSequence)) + } else { + return rawJsonToMap(rawJSON) + } + case pbcodec.DBOp_OPERATION_REMOVE: + if rawJSON, err := json.Marshal(decodedDBOp.OldJSON); err != nil { + zlog.Fatal("invalid dpOp", zap.Error(err), zap.Uint64("globalSequence", trace.Receipt.GlobalSequence)) + } else { + return rawJsonToMap(rawJSON) + } + default: + zlog.Fatal("unsupported operation type", zap.Int32("operation", int32(op))) + } + return nil + }, + } + return interpreter.NewActivation(activationMap) +} + +var ActionDeclarations = cel.Declarations( + decls.NewVar("receiver", decls.String), // eosio.account_name::receiver + decls.NewVar("account", decls.String), // eosio.account_name::account + decls.NewVar("action", decls.String), // eosio.name::action + + decls.NewVar("block_num", decls.Uint), // uint32 block number + decls.NewVar("block_id", decls.String), // string block id (hash) + decls.NewVar("block_time", decls.String), // string timestamp + + decls.NewVar("step", decls.String), // one of: Irreversible, New, Undo, Redo, Unknown + decls.NewVar("transaction_id", decls.String), // string transaction id (hash) + decls.NewVar("transaction_index", decls.Uint), // uint transaction position inside the block + decls.NewVar("global_seq", decls.Uint), // uint + decls.NewVar("execution_index", decls.Uint), // uint action position inside the transaction + + decls.NewVar("data", decls.NewMapType(decls.String, decls.Any)), + decls.NewVar("auth", decls.NewListType(decls.String)), + decls.NewVar("first_auth_actor", decls.NewListType(decls.String)), +) + +func NewActionActivation(stepName string, transaction *pbcodec.TransactionTrace, trace *pbcodec.ActionTrace) (interpreter.Activation, error) { + var activationMap = map[string]interface{}{ + "block_num": transaction.BlockNum, + "block_id": transaction.ProducerBlockId, + "block_time": func() interface{} { return transaction.BlockTime.AsTime().UTC().Format(time.RFC3339) }, + "transaction_id": transaction.Id, + "transaction_index": transaction.Index, + "step": stepName, + "global_seq": func() interface{} { + if trace.Receipt != nil { + return trace.Receipt.GlobalSequence + } + return 0 + }, + "execution_index": trace.ExecutionIndex, + "receiver": func() interface{} { + if trace.Receipt != nil { + return trace.Receipt.Receiver + } + return trace.Receiver + }, + "account": trace.Account(), + "action": trace.Name(), + "auth": func() interface{} { + return tokenizeEOSAuthority(trace.Action.Authorization) + }, + "data": func() interface{} { + jsonData := trace.Action.JsonData + if len(jsonData) == 0 || strings.IndexByte(jsonData, '{') == -1 { + return nil + } + + return jsonStringToMap(jsonData) + }, + "first_auth_actor": func() interface{} { + return getFirstAuthActor(transaction, trace) + }, + } + return interpreter.NewActivation(activationMap) +} + +func getFirstAuthActor(transaction *pbcodec.TransactionTrace, trace *pbcodec.ActionTrace) string { + if len(trace.Action.Authorization) > 0 { + return trace.Action.Authorization[0].Actor + } else { + parentOrdinal := trace.CreatorActionOrdinal + for _, entry := range transaction.ActionTraces { + if entry.ActionOrdinal == parentOrdinal { + return entry.Action.Authorization[0].Actor + } + } + panic(fmt.Sprintf("fail the execute first_auth_actor cannot find the parent action with ordinal: %d", parentOrdinal)) + } +} + +func jsonStringToMap(jsonData string) (out map[string]interface{}) { + var rawJSON = json.RawMessage(jsonData) + err := json.Unmarshal(rawJSON, &out) + if err != nil { + zlog.Error("invalid json data", zap.Error(err), zap.String("json", jsonData)) + + } + return +} + +func rawJsonToMap(rawJSON json.RawMessage) (out []map[string]interface{}) { + err := json.Unmarshal(rawJSON, &out) + if err != nil { + zlog.Error("invalid json data", zap.Error(err), zap.String("json", string(rawJSON))) + } + return +} + +var stringType = reflect.TypeOf("") +var stringArrayType = reflect.TypeOf([]string{}) + +func evalString(prog cel.Program, activation interface{}) (string, error) { + res, _, err := prog.Eval(activation) + if err != nil { + return "", err + } + out, err := res.ConvertToNative(stringType) + if err != nil { + return "", err + } + return out.(string), nil +} + +func evalStringArray(prog cel.Program, activation interface{}) ([]string, error) { + res, _, err := prog.Eval(activation) + if err != nil { + return nil, err + } + out, err := res.ConvertToNative(stringArrayType) + if err != nil { + return nil, err + } + return out.([]string), nil +} + func exprToCelProgram(stripped string) (prog cel.Program, err error) { - env, err := cel.NewEnv(filtering.ActionTraceDeclarations) + return exprToCelProgramWithEnv(stripped, Declarations) +} + +func exprToCelProgramWithEnv(stripped string, declarations cel.EnvOption) (prog cel.Program, err error) { + env, err := cel.NewEnv(declarations) if err != nil { return nil, fmt.Errorf("creating new CEL environment: %w", err) } @@ -22,6 +308,16 @@ func exprToCelProgram(stripped string) (prog cel.Program, err error) { if err != nil { return nil, fmt.Errorf("creating program from AST expression %s: %w", stripped, err) } + return +} +// This must follow rules taken in `search/tokenization.go`, ideally we would share this, maybe would be a good idea to +// put the logic in an helper method on type `pbcodec.PermissionLevel` directly. +func tokenizeEOSAuthority(authorizations []*pbcodec.PermissionLevel) (out []string) { + out = make([]string, len(authorizations)*2) + for i, auth := range authorizations { + out[i*2] = auth.Actor + out[i*2+1] = auth.Authorization() + } return } diff --git a/cel_test.go b/cel_test.go new file mode 100644 index 0000000..7df9b21 --- /dev/null +++ b/cel_test.go @@ -0,0 +1,442 @@ +package dkafka + +import ( + "context" + "encoding/json" + "reflect" + "testing" + + pbcodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/codec/v1" +) + +func Test_exprToCelProgram(t *testing.T) { + byteValue := readFileFromTestdata(t, "testdata/block-30080032.json") + + block := &pbcodec.Block{} + // must delete rlimit_ops, valid_block_signing_authority_v2, active_schedule_v2 + err := json.Unmarshal(byteValue, block) + if err != nil { + t.Fatalf("Unmarshal() error: %v", err) + } + var localABIFiles = map[string]string{ + "eosio.nft.ft": "testdata/eosio.nft.ft.abi", + } + abiFiles, err := LoadABIFiles(localABIFiles) + if err != nil { + t.Fatalf("LoadABIFiles() error: %v", err) + } + abiDecoder := NewABIDecoder(abiFiles, nil, context.Background()) + + transactionTrace := block.TransactionTraces()[0] + act := transactionTrace.ActionTraces[0] + decodedDBOps, err := abiDecoder.DecodeDBOps(transactionTrace.DBOpsForAction(act.ExecutionIndex), block.Number) + if err != nil { + t.Fatalf("DecodeDBOps() error: %v", err) + } + activation, err := NewActivation("NEW", transactionTrace, act, decodedDBOps) + if err != nil { + t.Fatalf("NewActivation() error: %v", err) + } + + type args struct { + expression string + } + tests := []struct { + name string + args args + wantString string + wantErr bool + }{ + { + "block_num", + args{ + "string(block_num)", + }, + "30080032", + false, + }, + { + "block_num", + args{ + "string(block_num)", + }, + "30080032", + false, + }, + { + "block_id", + args{ + "block_id", + }, + "01cafc203bf4bf807266fe5ac12c4b9ef72bd6482367a6c95bff70161dbeb462", + false, + }, + { + "block_time", + args{ + "block_time", + }, + "2021-11-17T10:16:19Z", + false, + }, + { + "transaction_id", + args{ + "transaction_id", + }, + "a2a53dce154c2ccdca52a981318775938de02f7efef88926ae1d7fd992988530", + false, + }, + { + "step", + args{ + "step", + }, + "NEW", + false, + }, + { + "global_seq", + args{ + "string(global_seq)", + }, + "80493723", + false, + }, + { + "execution_index", + args{ + "string(execution_index)", + }, + "0", + false, + }, + { + "receiver", + args{ + "receiver", + }, + "eosio.nft.ft", + false, + }, + { + "account", + args{ + "account", + }, + "eosio.nft.ft", + false, + }, + { + "action", + args{ + "action", + }, + "create", + false, + }, + { + "auth", + args{ + "auth[0]", + }, + "ultra.nft.ft", + false, + }, + { + "data", + args{ + "data.create.asset_creator", + }, + "ultra.nft.ft", + false, + }, + { + "db_ops", + args{ + "db_ops[0].table_name", + }, + "next.factory", + false, + }, + { + "db_ops", + args{ + "string(db_ops[0].old_json.value)", + }, + "26", + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotProg, err := exprToCelProgram(tt.args.expression) + if (err != nil) != tt.wantErr { + t.Errorf("exprToCelProgram() error = %v, wantErr %v", err, tt.wantErr) + return + } + gotString, err := evalString(gotProg, activation) + if (err != nil) != tt.wantErr { + t.Errorf("evalString() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotString, tt.wantString) { + t.Errorf("evalString() = %v, want %v", gotString, tt.wantString) + } + }) + } +} + +func Benchmark_exprToCelProgram_activation(b *testing.B) { + byteValue := readFileFromTestdata(b, "testdata/block-30080032.json") + + block := &pbcodec.Block{} + // must delete rlimit_ops, valid_block_signing_authority_v2, active_schedule_v2 + err := json.Unmarshal(byteValue, block) + if err != nil { + b.Fatalf("Unmarshal() error: %v", err) + } + var localABIFiles = map[string]string{ + "eosio.nft.ft": "testdata/eosio.nft.ft.abi", + } + abiFiles, err := LoadABIFiles(localABIFiles) + if err != nil { + b.Fatalf("LoadABIFiles() error: %v", err) + } + abiDecoder := NewABIDecoder(abiFiles, nil, context.Background()) + + transactionTrace := block.TransactionTraces()[0] + act := transactionTrace.ActionTraces[0] + decodedDBOps, err := abiDecoder.DecodeDBOps(transactionTrace.DBOpsForAction(act.ExecutionIndex), block.Number) + if err != nil { + b.Fatalf("DecodeDBOps() error: %v", err) + } + activation, err := NewActivation("New", transactionTrace, act, decodedDBOps) + if err != nil { + b.Fatalf("NewActivation() error: %v", err) + } + + type args struct { + expression string + } + tests := []struct { + name string + args args + wantString string + wantErr bool + }{ + { + "block_num", + args{ + "string(block_num)", + }, + "30080032", + false, + }, + { + "block_num", + args{ + "string(block_num)", + }, + "30080032", + false, + }, + { + "block_id", + args{ + "block_id", + }, + "01cafc203bf4bf807266fe5ac12c4b9ef72bd6482367a6c95bff70161dbeb462", + false, + }, + { + "block_time", + args{ + "block_time", + }, + "2021-11-17T10:16:19Z", + false, + }, + { + "transaction_id", + args{ + "transaction_id", + }, + "a2a53dce154c2ccdca52a981318775938de02f7efef88926ae1d7fd992988530", + false, + }, + { + "step", + args{ + "step", + }, + "New", + false, + }, + { + "global_seq", + args{ + "string(global_seq)", + }, + "80493723", + false, + }, + { + "execution_index", + args{ + "string(execution_index)", + }, + "0", + false, + }, + { + "receiver", + args{ + "receiver", + }, + "eosio.nft.ft", + false, + }, + { + "account", + args{ + "account", + }, + "eosio.nft.ft", + false, + }, + { + "action", + args{ + "action", + }, + "create", + false, + }, + { + "auth", + args{ + "auth[0]", + }, + "ultra.nft.ft", + false, + }, + { + "data", + args{ + "data.create.asset_creator", + }, + "ultra.nft.ft", + false, + }, + { + "db_ops", + args{ + "db_ops[0].table_name", + }, + "next.factory", + false, + }, + } + for _, tt := range tests { + gotProg, err := exprToCelProgram(tt.args.expression) + if (err != nil) != tt.wantErr { + b.Errorf("exprToCelProgram() error = %v, wantErr %v", err, tt.wantErr) + return + } + b.Run(tt.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := evalString(gotProg, activation) + if err != nil { + b.Errorf("evalString() error = %v, wantErr %v", err, tt.wantErr) + return + } + } + }) + } +} + +func Test_getFirstAuthActor(t *testing.T) { + type args struct { + transaction *pbcodec.TransactionTrace + trace *pbcodec.ActionTrace + } + tests := []struct { + name string + args args + want string + panic bool + }{ + { + "default", + args{ + &pbcodec.TransactionTrace{}, + &pbcodec.ActionTrace{ + Action: &pbcodec.Action{ + Authorization: []*pbcodec.PermissionLevel{{Actor: "vincent"}}, + }, + }, + }, + "vincent", + false, + }, + { + "fallback", + args{ + &pbcodec.TransactionTrace{ + ActionTraces: []*pbcodec.ActionTrace{{ + ActionOrdinal: 1, + Action: &pbcodec.Action{ + Authorization: []*pbcodec.PermissionLevel{{Actor: "olivier"}}, + }, + }}, + }, + &pbcodec.ActionTrace{ + ActionOrdinal: 2, + Action: &pbcodec.Action{}, + CreatorActionOrdinal: 1, + }, + }, + "olivier", + false, + }, + { + "panic", + args{ + &pbcodec.TransactionTrace{ + ActionTraces: []*pbcodec.ActionTrace{{ + ActionOrdinal: 1, + }}, + }, + &pbcodec.ActionTrace{ + ActionOrdinal: 3, + Action: &pbcodec.Action{}, + CreatorActionOrdinal: 2, + }, + }, + "", + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.panic { + testPanic := func() { + getFirstAuthActor(tt.args.transaction, tt.args.trace) + } + assertPanic(t, testPanic) + } else if got := getFirstAuthActor(tt.args.transaction, tt.args.trace); got != tt.want { + t.Errorf("getFirstAuthActor() = %v, want %v", got, tt.want) + } + }) + } +} + +func assertPanic(t *testing.T, f func()) { + defer func() { + if r := recover(); r == nil { + t.Errorf("The code did not panic") + } + }() + f() +} diff --git a/checkpoint.go b/checkpoint.go index 98fb9e9..70710ae 100644 --- a/checkpoint.go +++ b/checkpoint.go @@ -1,36 +1,125 @@ package dkafka import ( - "context" "encoding/json" "errors" "fmt" - "log" "strings" "time" "github.com/confluentinc/confluent-kafka-go/kafka" + "github.com/streamingfast/bstream" + "github.com/streamingfast/bstream/forkable" "go.uber.org/zap" ) -var NoCursorErr = errors.New("no cursor exists") +const dkafkaCheckpoint = "DKafkaCheckpoint" + +var blockRef = RecordSchema{ + Type: "record", + Name: "BlockRef", + Namespace: dkafkaNamespace, + Doc: "BlockRef represents a reference to a block and is mainly define as the pair ", + Fields: []FieldSchema{ + { + Name: "id", + Type: "string", + }, + { + Name: "num", + Type: "long", + }, + }, +} -type checkpointer interface { - Save(cursor string) error - Load() (cursor string, err error) +func newBlockRefMap(blockRef bstream.BlockRef) map[string]interface{} { + return map[string]interface{}{ + "id": blockRef.ID(), + "num": blockRef.Num(), + } } -type nilCheckpointer struct{} +var CheckpointSchema = RecordSchema{ + Type: "record", + Name: dkafkaCheckpoint, + Namespace: dkafkaNamespace, + Doc: "Periodically emitted checkpoint used to save the current position", + Fields: []FieldSchema{ + { + Name: "step", + Type: "int", + Doc: `Step of the current block value can be: 1(New),2(Undo),3(Redo),4(Handoff),5(Irreversible),6(Stalled) + - 1(New): First time we're seeing this block + - 2(Undo): We are undoing this block (it was done previously) + - 4(Redo): We are redoing this block (it was done previously) + - 8(Handoff): The block passed a handoff from one producer to another + - 16(Irreversible): This block passed the LIB barrier and is in chain + - 32(Stalled): This block passed the LIB and is definitely forked out +`, + }, + { + Name: "block", + Type: blockRef, + }, + { + Name: "headBlock", + Type: "BlockRef", + }, + { + Name: "lastIrreversibleBlock", + Type: "BlockRef", + }, + { + Name: "time", + Type: map[string]string{ + "type": "long", + "logicalType": "timestamp-millis", + }, + }, + }, +} -func (n *nilCheckpointer) Save(string) error { - return nil +type dkafkaMetaSupplier struct { } -func (n *nilCheckpointer) Load() (string, error) { - return "", NoCursorErr +func (dms dkafkaMetaSupplier) GetVersion() string { + return "1.0.0" +} +func (dms dkafkaMetaSupplier) GetSource() string { + return "dkafka-cli" +} +func (dms dkafkaMetaSupplier) GetDomain() string { + return "dkafka" +} +func (dms dkafkaMetaSupplier) GetCompatibility() string { + return "FORWARD" +} +func (dms dkafkaMetaSupplier) GetType() string { + return "notification" +} + +var CheckpointMessageSchema = MessageSchema{ + CheckpointSchema, + newMeta(dkafkaMetaSupplier{}), } -func newKafkaCheckpointer(conf kafka.ConfigMap, cursorTopic string, cursorPartition int32, dataTopic string, consumerGroupID string, producer *kafka.Producer) *kafkaCheckpointer { +func newCheckpointMap(cursor *forkable.Cursor, time time.Time) map[string]interface{} { + return map[string]interface{}{ + "step": int(cursor.Step), + "block": newBlockRefMap(cursor.Block), + "headBlock": newBlockRefMap(cursor.HeadBlock), + "lastIrreversibleBlock": newBlockRefMap(cursor.LIB), + "time": time, + } +} + +var ErrNoCursor = errors.New("no cursor exists") + +type checkpointer interface { + Load() (cursor string, err error) +} + +func newKafkaCheckpointer(conf kafka.ConfigMap, cursorTopic string, cursorPartition int32, dataTopic string, consumerGroupID string) checkpointer { consumerConfig := cloneConfig(conf) id := strings.Replace(fmt.Sprintf("dk-%s-%s-%d", dataTopic, cursorTopic, cursorPartition), "_", "", -1) @@ -42,63 +131,22 @@ func newKafkaCheckpointer(conf kafka.ConfigMap, cursorTopic string, cursorPartit topic: cursorTopic, partition: cursorPartition, key: []byte(id), - producer: producer, } } type kafkaCheckpointer struct { key []byte - producer *kafka.Producer consumerConfig kafka.ConfigMap topic string partition int32 } -// in case we need it -//func newFileCheckpointer(filename string) *localFileCheckpointer { -// return &localFileCheckpointer{ -// filename: filename, -// } -//} -// -//type localFileCheckpointer struct { -// filename string -//} -// -//func (c *localFileCheckpointer) Save(cursor string) error { -// dat := []byte(cursor) -// return ioutil.WriteFile(c.filename, dat, 0644) -//} -// -//func (c *localFileCheckpointer) Load() (string, error) { -// dat, err := ioutil.ReadFile(c.filename) -// if os.IsNotExist(err) { -// return "", NoCursorErr -// } -// return string(dat), err -//} - type cs struct { Cursor string `json:"cursor"` } -func (c *kafkaCheckpointer) Save(cursor string) error { - v, err := json.Marshal(cs{Cursor: cursor}) - if err != nil { - return err - } - msg := &kafka.Message{ - Key: c.key, - TopicPartition: kafka.TopicPartition{ - Topic: &c.topic, - Partition: c.partition, - }, - Value: v, - } - return c.producer.Produce(msg, nil) -} - func (c *kafkaCheckpointer) Load() (string, error) { + zlog.Info("try to load cursor from cursor topic", zap.String("cursor_topic", c.topic), zap.Int32("cursor_partition", c.partition)) consumer, err := kafka.NewConsumer(&c.consumerConfig) if err != nil { return "", fmt.Errorf("creating consumer: %w", err) @@ -106,7 +154,7 @@ func (c *kafkaCheckpointer) Load() (string, error) { defer func() { if err := consumer.Close(); err != nil { - log.Printf("error closing consumer: %s", err) + zlog.Error("error closing consumer", zap.Error(err)) } }() @@ -117,14 +165,9 @@ func (c *kafkaCheckpointer) Load() (string, error) { return "", fmt.Errorf("getting metadata: %w", err) } parts := md.Topics[c.topic].Partitions - if len(parts) == 0 { - zlog.Info("cursor topic does not exist, creating", zap.String("cursor_topic", c.topic)) - err := createKafkaCursorTopic(consumer, c.topic, len(md.Brokers)) - if err != nil { - return "", err - } - } else if len(parts)-1 < int(c.partition) { - return "", fmt.Errorf("requested cursor partition does not exist in cursor topic") + if len(parts)-1 < int(c.partition) { + zlog.Info("requested topic or partition does not exist for cursor topic", zap.String("cursor_topic", c.topic), zap.Int32("cursor_partition", c.partition)) + return "", ErrNoCursor } low, high, err := consumer.QueryWatermarkOffsets(c.topic, c.partition, 500) @@ -132,9 +175,9 @@ func (c *kafkaCheckpointer) Load() (string, error) { return "", fmt.Errorf("getting low/high: %w", err) } - for i := kafka.Offset(high) - 1; i >= kafka.Offset(low); i-- { + for i := kafka.Offset(high) - 1; (i >= kafka.Offset(low)) && ((kafka.Offset(high) - i) > 50); i-- { err = consumer.Assign([]kafka.TopicPartition{ - kafka.TopicPartition{ + { Topic: &c.topic, Partition: c.partition, Offset: i, @@ -159,13 +202,13 @@ func (c *kafkaCheckpointer) Load() (string, error) { } } if cursor.Cursor == "" { - err = NoCursorErr + err = ErrNoCursor } return cursor.Cursor, err default: } } - return "", NoCursorErr + return "", ErrNoCursor } func cloneConfig(in kafka.ConfigMap) kafka.ConfigMap { @@ -175,32 +218,3 @@ func cloneConfig(in kafka.ConfigMap) kafka.ConfigMap { } return out } - -func createKafkaCursorTopic(c *kafka.Consumer, cursorTopic string, maxAvailableBrokers int) error { - adminCli, err := kafka.NewAdminClientFromConsumer(c) - if err != nil { - return fmt.Errorf("creating admin client: %w", err) - } - numParts := 10 - replicationFactor := 3 - if replicationFactor > maxAvailableBrokers { - replicationFactor = maxAvailableBrokers - } - - results, err := adminCli.CreateTopics( - context.Background(), - // Multiple topics can be created simultaneously - // by providing more TopicSpecification structs here. - []kafka.TopicSpecification{{ - Topic: cursorTopic, - NumPartitions: numParts, - ReplicationFactor: replicationFactor}}, - // Admin options - kafka.SetAdminOperationTimeout(time.Second*10)) - if err != nil { - return fmt.Errorf("creating topic: %w", err) - } - - zlog.Info("creating topic", zap.Any("results", results), zap.Int("num_partitions", numParts), zap.Int("replication_factor", replicationFactor)) - return nil -} diff --git a/cmd/dkafka/cdc.go b/cmd/dkafka/cdc.go new file mode 100644 index 0000000..8c69769 --- /dev/null +++ b/cmd/dkafka/cdc.go @@ -0,0 +1,380 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/dfuse-io/dkafka" + "github.com/iancoleman/strcase" + "github.com/riferrei/srclient" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/streamingfast/derr" + "go.uber.org/zap" +) + +var codecTypes = NewEnumFlag(dkafka.JsonCodec, dkafka.AvroCodec) +var compatibilityTypes = NewEnumFlag( + srclient.Forward.String(), // Default compatibility + srclient.None.String(), + srclient.Full.String(), + srclient.Backward.String(), + srclient.BackwardTransitive.String(), + srclient.ForwardTransitive.String(), +) + +type GenOptions struct { + namespace string + version string + outputDir string + abiSpec *dkafka.ABI +} + +var CdCCmd = &cobra.Command{ + Use: "cdc", + Short: "Change Data Capture", + Long: "Change Data Capture", +} + +var CdCTablesCmd = &cobra.Command{ + Use: dkafka.TABLES_CDC_TYPE, + Short: "Change Data Capture on smart contract tables", + Long: `Change Data Capture on smart contract tables. +This argument is the smart contract account to capture.`, + Args: cobra.ExactArgs(1), + ValidArgs: []string{"account"}, + RunE: cdcOnTables, +} + +var CdCActionsCmd = &cobra.Command{ + Use: dkafka.ACTIONS_CDC_TYPE, + Short: "Change Data Capture on smart contract actions", + Long: `Change Data Capture on smart contract actions. +This argument is the smart contract account to capture.`, + Args: cobra.ExactArgs(1), + ValidArgs: []string{"account"}, + RunE: cdcOnActions, +} + +var CdCTransactionsCmd = &cobra.Command{ + Use: dkafka.TRANSACTION_CDC_TYPE, + Short: "Change Data Capture on block transactions", + Long: `Change Data Capture on block transactions. +Produces one message per transaction in a block.`, + Args: cobra.ExactArgs(0), + RunE: cdcOnTransactions, +} + +var CdCSchemasCmd = &cobra.Command{ + Use: "schemas [-n namespace] [-V version] [-o output-dir] abi-file-def", + Short: "Generate all tables and actions messages avro schemas from ABI file definition", + Long: `Generate all tables and actions messages avro schemas from ABI file definition. The ABI file argument (abi-file-def) +must be in this format: +'{account}:{path/to/filename}' (ex: 'eosio.token:/tmp/eosio_token.abi'). +The schema type (CamelCase) is for: +- table: Notification +- action: Notification`, + Args: cobra.ExactArgs(1), + ValidArgs: []string{"abi-file-path"}, + Run: generateAllAvroSchema, +} + +func init() { + RootCmd.AddCommand(CdCCmd) + // cursor migration flags + CdCCmd.PersistentFlags().String("kafka-cursor-topic", "", `kafka topic where cursor were saved by a previous version of dkafka. +This option can be used when you want to migrate from a previous version +of dkafka that was using the cursor topic to save checkpoints`) + CdCCmd.Flags().Uint32("kafka-cursor-partition", 0, "kafka partition where cursor will be loaded and saved") + CdCCmd.Flags().String("kafka-cursor-consumer-group-id", "dkafkaconsumer", "Consumer group ID for reading cursor") + //--- + CdCCmd.PersistentFlags().Var(compressionTypes, "kafka-compression-type", compressionTypes.Help("Specify the compression type to use for compressing message sets.")) + CdCCmd.PersistentFlags().Int8("kafka-compression-level", int8(-1), `Compression level parameter for algorithm selected by configuration property +kafka-compression-type. Higher values will result in better compression at the +cost of more CPU usage. Usable range is algorithm-dependent: [0-9] for gzip; +[0-12] for lz4; only 0 for snappy; -1 = codec-dependent default compression +level.`) + CdCCmd.PersistentFlags().Int("kafka-message-max-bytes", 1_000_000, `Maximum Kafka protocol request message size. +Due to different framing overhead between protocol versions the producer is +unable to reliably enforce a strict max message limit at produce time, +the message size is checked against your raw uncompressed message. +The broker will also enforce the the topic's max.message.bytes limit +upon receiving your message. So make sure your brokers configuration +match your producers (same apply for consumers) +(see Apache Kafka documentation).`) + + CdCCmd.PersistentFlags().Bool("batch-mode", false, "Batch mode will ignore cursor and always start from {start-block-num}.") + CdCCmd.PersistentFlags().Int64("start-block-num", 0, `If we are in {batch-mode} or no prior cursor exists, +start streaming from this block number (if negative, relative to HEAD)`) + CdCCmd.PersistentFlags().Uint64("stop-block-num", 0, "If non-zero, stop processing before this block number") + CdCCmd.PersistentFlags().Bool("capture", false, "Activate the capture mode where blocks are saved on the file system in pb.json format.") + + CdCCmd.PersistentFlags().Duration("delay-between-commits", time.Second*10, "no commits to kafka blow this delay, except un shutdown") + CdCCmd.PersistentFlags().String("event-source", "", "custom value for produced cloudevent source. If not specified then the host name will be used.") + + CdCCmd.PersistentFlags().Bool("executed", false, `Specify publish messages based only on executed actions => modify the state of the blockchain. +This remove the error messages`) + CdCCmd.PersistentFlags().Bool("irreversible", false, "Specify publish messages based only on irreversible actions") + CdCCmd.PersistentFlags().Bool("force", false, "Will force the usage of the blocknumber provided instead of the saved cursor.") + + CdCCmd.PersistentFlags().Var(codecTypes, "codec", codecTypes.Help("Specify the codec to use to encode the messages.")) + CdCCmd.PersistentFlags().String("schema-registry-url", "http://localhost:8081", "Schema registry url whose schemas are pushed to") + CdCCmd.PersistentFlags().Var(compatibilityTypes, "compatibility", compatibilityTypes.Help("Specify the compatibility mode for the schema registry subjects.")) + + CdCCmd.PersistentFlags().StringP("namespace", "n", "", "namespace of the schema(s). Default: account name") + CdCCmd.PersistentFlags().Uint("major-version", 0, "Optional but strongly recommended if --version of the schema(s) is not used. It is used in conjunction with the ABI Block Number to version the schema in semver form: [Major].[ABIBlockNumber].0") + CdCCmd.PersistentFlags().StringP("version", "V", "", "Optional but strongly recommended version of the schema(s) in a semver form: 1.2.3.") + CdCCmd.PersistentFlags().StringSlice("local-abi-files", []string{}, `repeatable, ABI file definition in this format: +'{account}:{path/to/filename}[:{block-number}]' (ex: 'eosio.token:/tmp/eosio_token.abi[:3]'). +ABIs are used to decode DB ops. Provided ABIs have highest priority and +will never be fetched or updated. Block number, being the default value: 0, will be used for versioning in the ABI schema.`) + CdCCmd.PersistentFlags().String("abicodec-grpc-addr", "", "if set, will connect to this endpoint to fetch contract ABIs") + + CdCCmd.AddCommand(CdCActionsCmd) + CdCActionsCmd.Flags().String("actions-expr", "", "A JSON Object that associate the a name of an action to CEL expression for the message key extraction.") + CdCActionsCmd.Flags().Bool("skip-dbops", false, "Will skip the generation of the DbOps array and generate an empty array. This is useful when you only want to capture the action and not the DbOps and when there is too many DbOps.") + + CdCCmd.AddCommand(CdCTablesCmd) + CdCTablesCmd.Flags().StringSlice("table-name", []string{}, `table name(s) on which the message must be produced. +The name can include the key extractor pattern as follow: +{|*}[:{k|s|s+k}]. Where k is for DBOp.PrimaryKey +and s is for DBOp.Scope.If not specified the ':k' extractor +is used. '*' can be used as a table name to specify any tables. +You can mix '*' with specific table name(s) if you want to +apply another key mapping than the one for the wildcard on +a subset of tables. You can also specify key mapping with +the wildcard. +Example: --table-name=factory.a:k,token.a:k,*:s+k`) + + CdCCmd.AddCommand(CdCSchemasCmd) + CdCSchemasCmd.Flags().StringP("output-dir", "o", "./", `Optional output directory for the avro schema. The file name pattern is + the -.avsc in snake-case.`) + CdCCmd.AddCommand(CdCTransactionsCmd) +} + +func cdcOnTransactions(cmd *cobra.Command, args []string) error { + SetupLogger() + zlog.Debug("CDC on transaction") + return executeCdC(cmd, args, dkafka.TRANSACTION_CDC_TYPE, func(c *dkafka.Config, args []string) *dkafka.Config { + return c + }) +} + +func cdcOnTables(cmd *cobra.Command, args []string) error { + SetupLogger() + account := args[0] + zlog.Debug( + "CDC on table", + zap.String("account", account), + ) + return executeCdC(cmd, args, dkafka.TABLES_CDC_TYPE, configAccount(func(c *dkafka.Config) *dkafka.Config { + c.TableNames = viper.GetStringSlice("cdc-tables-cmd-table-name") + return c + })) +} + +func cdcOnActions(cmd *cobra.Command, args []string) error { + SetupLogger() + return executeCdC(cmd, args, dkafka.ACTIONS_CDC_TYPE, configAccount(func(c *dkafka.Config) *dkafka.Config { + c.ActionExpressions = viper.GetString("cdc-actions-cmd-actions-expr") + c.SkipDbOps = viper.GetBool("cdc-actions-cmd-skip-dbops") + return c + })) +} + +func configAccount(f func(*dkafka.Config) *dkafka.Config) func(*dkafka.Config, []string) *dkafka.Config { + return func(c *dkafka.Config, args []string) *dkafka.Config { + account := args[0] + zlog.Debug( + "CDC on action", + zap.String("account", account), + ) + c.Account = account + return f(c) + } +} + +func generateAllAvroSchema(cmd *cobra.Command, args []string) { + SetupLogger() + opts, err := genOptions(cmd, args) + if err != nil { + zlog.Fatal("action generetion fail", zap.Error(err)) + } + zlog.Info("generate all schemas for:", zap.String("account", opts.abiSpec.Account)) + zlog.Info("generate all Actions") + for _, action := range opts.abiSpec.Actions { + err = doGenActionAvroSchema(action.Name.String(), opts) + if err != nil { + zlog.Fatal("doGenActionAvroSchema()", zap.Error(err)) + } + } + zlog.Info("generate all Tables") + for _, table := range opts.abiSpec.Tables { + err = doGenTableAvroSchema(string(table.Name), opts) + if err != nil { + zlog.Fatal("doGenTableAvroSchema()", zap.Error(err)) + } + + } + zlog.Info("generate static dkafka schemas") + if err = saveSchema(dkafka.CheckpointMessageSchema, "", opts.outputDir); err != nil { + zlog.Fatal("fail to saveSchema()", zap.String("schema", dkafka.CheckpointMessageSchema.Name), zap.Error(err)) + } + if err = saveSchema(dkafka.TransactionMessageSchema, "", opts.outputDir); err != nil { + zlog.Fatal("fail to saveSchema()", zap.String("schema", dkafka.TransactionMessageSchema.Name), zap.Error(err)) + } +} + +func executeCdC(cmd *cobra.Command, args []string, + cdcType string, f func(*dkafka.Config, []string) *dkafka.Config) error { + localABIFiles, err := dkafka.ParseABIFileSpecs(viper.GetStringSlice("cdc-cmd-local-abi-files")) + if err != nil { + return err + } + + conf := &dkafka.Config{ + DfuseToken: viper.GetString("global-dfuse-auth-token"), + DfuseGRPCEndpoint: viper.GetString("global-dfuse-firehose-grpc-addr"), + IncludeFilterExpr: viper.GetString("global-dfuse-firehose-include-expr"), + + DryRun: viper.GetBool("global-dry-run"), + KafkaEndpoints: viper.GetString("global-kafka-endpoints"), + KafkaSSLEnable: viper.GetBool("global-kafka-ssl-enable"), + KafkaSSLCAFile: viper.GetString("global-kafka-ssl-ca-file"), + KafkaSSLAuth: viper.GetBool("global-kafka-ssl-auth"), + KafkaSSLClientCertFile: viper.GetString("global-kafka-ssl-client-cert-file"), + KafkaSSLClientKeyFile: viper.GetString("global-kafka-ssl-client-key-file"), + KafkaTopic: viper.GetString("global-kafka-topic"), + KafkaCursorTopic: viper.GetString("cdc-cmd-kafka-cursor-topic"), + KafkaCursorPartition: int32(viper.GetUint32("cdc-cmd-kafka-cursor-partition")), + KafkaCursorConsumerGroupID: viper.GetString("cdc-cmd-kafka-cursor-consumer-group-id"), + KafkaCompressionType: viper.GetString("cdc-cmd-kafka-compression-type"), + KafkaCompressionLevel: viper.GetInt("cdc-cmd-kafka-compression-level"), + KafkaMessageMaxBytes: viper.GetInt("cdc-cmd-kafka-message-max-bytes"), + CommitMinDelay: viper.GetDuration("cdc-cmd-delay-between-commits"), + + BatchMode: viper.GetBool("cdc-cmd-batch-mode"), + StartBlockNum: viper.GetInt64("cdc-cmd-start-block-num"), + StopBlockNum: viper.GetUint64("cdc-cmd-stop-block-num"), + StateFile: viper.GetString("cdc-cmd-state-file"), + Capture: viper.GetBool("cdc-cmd-capture"), + Force: viper.GetBool("cdc-cmd-force"), + + EventSource: viper.GetString("cdc-cmd-event-source"), + + CdCType: cdcType, + Irreversible: viper.GetBool("cdc-cmd-irreversible"), + Executed: viper.GetBool("cdc-cmd-executed"), + + Codec: viper.GetString("cdc-cmd-codec"), + SchemaRegistryURL: viper.GetString("cdc-cmd-schema-registry-url"), + SchemaNamespace: viper.GetString("cdc-cmd-namespace"), + SchemaMajorVersion: viper.GetUint("cdc-cmd-major-version"), + SchemaVersion: viper.GetString("cdc-cmd-version"), + Compatibility: viper.GetString("cdc-cmd-compatibility"), + LocalABIFiles: localABIFiles, + ABICodecGRPCAddr: viper.GetString("cdc-cmd-abicodec-grpc-addr"), + } + conf = f(conf, args) + cmd.SilenceUsage = true + signalHandler := derr.SetupSignalHandler(time.Second) + + zlog.Info("starting dkafka publisher", zap.Reflect("config", conf)) + app := dkafka.New(conf) + go func() { app.Shutdown(app.Run()) }() + + select { + case <-signalHandler: + app.Shutdown(fmt.Errorf("shutdown signal received")) + case <-app.Terminating(): + } + zlog.Info("terminating", zap.Error(app.Err())) + + <-app.Terminated() + return app.Err() +} + +func genOptions(cmd *cobra.Command, args []string) (opts GenOptions, err error) { + namespace := viper.GetString("cdc-cmd-namespace") + outputDir := viper.GetString("cdc-schemas-cmd-output-dir") + version := viper.GetString("cdc-cmd-version") + abiString := args[0] + + account, abiFile, err := dkafka.ParseABIFileSpec(abiString) + if err != nil { + return + } + + if _, err = os.Stat(outputDir); err != nil { + err = fmt.Errorf("cannot reach %s error: %v", outputDir, err) + return + } + + zlog.Debug( + "global avro gen options", + zap.String("namespace", namespace), + zap.String("version", version), + zap.String("outputDir", outputDir), + zap.String("abi", abiString), + ) + + abiSpec, err := dkafka.LoadABIFile(account, abiFile) + if err != nil { + return + } + + return GenOptions{ + namespace: namespace, + version: version, + outputDir: outputDir, + abiSpec: abiSpec, + }, nil +} + +func doGenActionAvroSchema(name string, opts GenOptions) error { + zlog.Info("generate schema for:", zap.String("account", opts.abiSpec.Account), zap.String("action", name)) + return doGenAvroSchema(name, opts, dkafka.GenerateActionSchema) +} + +func doGenTableAvroSchema(name string, opts GenOptions) error { + zlog.Info("generate schema for:", zap.String("account", opts.abiSpec.Account), zap.String("table", name)) + return doGenAvroSchema(name, opts, dkafka.GenerateTableSchema) +} + +func doGenAvroSchema(name string, opts GenOptions, f func(dkafka.NamedSchemaGenOptions) (dkafka.MessageSchema, error)) error { + schema, err := f(dkafka.NamedSchemaGenOptions{ + Name: name, + Namespace: opts.namespace, + Version: opts.version, + AbiSpec: opts.abiSpec, + Domain: opts.abiSpec.Account, + }) + if err != nil { + return fmt.Errorf("generation error: %v", err) + } + zlog.Debug("dkafka.GenerateActionSchema()", zap.Any("schema", schema), zap.Error(err)) + err = saveSchema(schema, opts.abiSpec.Account, opts.outputDir) + if err != nil { + return fmt.Errorf("saveSchema() error: %v", err) + } + return nil +} + +func saveSchema(schema dkafka.MessageSchema, prefix string, outputDir string) error { + fileName := strcase.ToKebab(fmt.Sprintf("%s%s", prefix, schema.Name)) + fileName = fmt.Sprintf("%s.avsc", fileName) + filePath := filepath.Join(outputDir, fileName) + zlog.Info("save schema", zap.String("path", filePath)) + jsonString, err := json.Marshal(schema) + if err != nil { + return fmt.Errorf("cannot convert schema to json error: %v", err) + } + os.WriteFile(filePath, jsonString, 0664) + if err != nil { + return fmt.Errorf("cannot write schema to '%s', error: %v", filePath, err) + } + return nil +} diff --git a/cmd/dkafka/debug.go b/cmd/dkafka/debug.go deleted file mode 100644 index 45ffb7c..0000000 --- a/cmd/dkafka/debug.go +++ /dev/null @@ -1,156 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/dfuse-io/dkafka" - "github.com/spf13/cobra" - "github.com/spf13/viper" - "go.uber.org/zap" -) - -var DebugCmd = &cobra.Command{ - Use: "debug", - Short: "", - Long: "", -} - -var CursorCmd = &cobra.Command{ - Use: "cursor", - Short: "", - Long: "", -} - -var CursorReadCmd = &cobra.Command{ - Use: "read", - Short: "", - Long: "", - RunE: cursorReadE, -} - -var CursorDeleteCmd = &cobra.Command{ - Use: "delete", - Short: "", - Long: "", - RunE: cursorDeleteE, -} - -var CursorWriteCmd = &cobra.Command{ - Use: "write", - Short: "", - Long: "", - RunE: cursorWriteE, -} - -var DebugWriteCmd = &cobra.Command{ - Use: "write", - Short: "", - Long: "", - RunE: debugWriteE, -} - -var DebugReadCmd = &cobra.Command{ - Use: "read", - Short: "", - Long: "", - RunE: debugReadE, -} - -func init() { - RootCmd.AddCommand(DebugCmd) - - DebugCmd.AddCommand(DebugWriteCmd) - DebugCmd.AddCommand(DebugReadCmd) - - DebugWriteCmd.Flags().String("key", "debug", "key to write in kafka") - DebugWriteCmd.Flags().String("value", "{\"from\":\"dkafka\"}", "value to write in kafka") - - DebugReadCmd.Flags().Int("values", 5, "number of values to read from kafka") - DebugReadCmd.Flags().Int("offset", -1, "if >= 0, set this value as starting offset") - DebugReadCmd.Flags().String("group-id", "dkafkadebug", "group ID to use as consumer") - - RootCmd.AddCommand(CursorCmd) - CursorCmd.AddCommand(CursorReadCmd) - CursorCmd.AddCommand(CursorDeleteCmd) - CursorCmd.AddCommand(CursorWriteCmd) - -} - -func getDkafkaConf() *dkafka.Config { - return &dkafka.Config{ - KafkaEndpoints: viper.GetString("global-kafka-endpoints"), - KafkaSSLEnable: viper.GetBool("global-kafka-ssl-enable"), - KafkaSSLCAFile: viper.GetString("global-kafka-ssl-ca-file"), - KafkaSSLAuth: viper.GetBool("global-kafka-ssl-auth"), - KafkaSSLClientCertFile: viper.GetString("global-kafka-ssl-client-cert-file"), - KafkaSSLClientKeyFile: viper.GetString("global-kafka-ssl-client-key-file"), - KafkaTopic: viper.GetString("global-kafka-topic"), - KafkaTransactionID: viper.GetString("global-kafka-transaction-id"), - - KafkaCursorTopic: viper.GetString("global-kafka-cursor-topic"), - KafkaCursorPartition: int32(viper.GetUint32("global-kafka-cursor-partition")), - KafkaCursorConsumerGroupID: viper.GetString("global-kafka-cursor-consumer-group-id"), - } -} - -func debugWriteE(cmd *cobra.Command, args []string) error { - SetupLogger() - - conf := getDkafkaConf() - key := viper.GetString("debug-write-cmd-key") - value := viper.GetString("debug-write-cmd-value") - - zlog.Info("writing debug value to kafka", zap.Reflect("config", conf), zap.String("key", key), zap.String("value", value)) - cmd.SilenceUsage = true - debugger := dkafka.NewDebugger(conf) - return debugger.Write(key, value) -} - -func debugReadE(cmd *cobra.Command, args []string) error { - SetupLogger() - - conf := getDkafkaConf() - values := viper.GetInt("debug-read-cmd-values") - offset := viper.GetInt("debug-read-cmd-offset") - groupID := viper.GetString("debug-read-cmd-group-id") - - zlog.Info("reading debug values from kafka", zap.Reflect("config", conf), zap.String("group_id", groupID), zap.Int("values", values), zap.Int("offset", offset)) - cmd.SilenceUsage = true - debugger := dkafka.NewDebugger(conf) - return debugger.Read(groupID, values, offset) -} - -func cursorReadE(cmd *cobra.Command, args []string) error { - SetupLogger() - - conf := getDkafkaConf() - - zlog.Info("reading cursor from kafka", zap.Reflect("config", conf)) - cmd.SilenceUsage = true - debugger := dkafka.NewDebugger(conf) - return debugger.ReadCursor() -} - -func cursorWriteE(cmd *cobra.Command, args []string) error { - SetupLogger() - - conf := getDkafkaConf() - if len(args) != 1 { - return fmt.Errorf("cursor write command requires exactly one argument: cursorvalue") - } - - zlog.Info("writing cursor value from kafka", zap.Reflect("config", conf), zap.String("cursor", args[0])) - cmd.SilenceUsage = true - debugger := dkafka.NewDebugger(conf) - return debugger.WriteCursor(args[0]) -} -func cursorDeleteE(cmd *cobra.Command, args []string) error { - SetupLogger() - - conf := getDkafkaConf() - - zlog.Info("reading debug values from kafka", zap.Reflect("config", conf)) - cmd.SilenceUsage = true - debugger := dkafka.NewDebugger(conf) - return debugger.DeleteCursor() -} diff --git a/cmd/dkafka/enumflag.go b/cmd/dkafka/enumflag.go new file mode 100644 index 0000000..faa472d --- /dev/null +++ b/cmd/dkafka/enumflag.go @@ -0,0 +1,87 @@ +// Package enumflag defines a flag.Value implementation that accepts one of a +// specified collection of string keys. Values are compared without respect to +// case, so that "foo" and "Foo" are accepted as equivalent to "FOO". +// +// Example: +// import ( +// "flag" +// +// "github.com/creachadair/goflags/enum" +// ) +// +// // The first enumerated value is the default. +// var color = enum.Flag("", "red", "orange", "yellow", "green", "blue") +// func init() { +// flag.Var(color, "color", color.Help("What color to paint the bikeshed")) +// } +// +package main + +import ( + "fmt" + "strings" +) + +// A Value represents an enumeration of string values. A pointer to a Value +// satisfies the flag.Value interface. Use the Key method to recover the +// currently-selected value of the enumeration. +type Value struct { + keys []string + index int // The selected index in the enumeration +} + +// Help concatenates a human-readable string summarizing the legal values of v +// to h, for use in generating a documentation string. +func (v Value) Help(h string) string { + return fmt.Sprintf("%s (%s)", h, strings.Join(v.keys, "|")) +} + +// New returns a *Value for the specified enumerators, where defaultKey is the +// default value and otherKeys are additional options. The index of a selected +// key reflects its position in the order given to this function, so that if: +// +// v := enumflag.New("a", "b", "c", "d") +// +// then the index of "a" is 0, "b" is 1, "c" is 2, "d" is 3. The default key is +// always stored at index 0. +func NewEnumFlag(defaultKey string, otherKeys ...string) *Value { + return &Value{keys: append([]string{defaultKey}, otherKeys...)} +} + +// Key returns the currently-selected key in the enumeration. The original +// spelling of the selected value is returned, as given to the Flag +// constructor, not the value as parsed. +func (v Value) Key() string { + if len(v.keys) == 0 { + return "" // BUG: https://github.com/golang/go/issues/16694 + } + return v.keys[v.index] +} + +// Get satisfies the flag.Getter interface. +// The concrete value is the the string of the current key. +func (v Value) Get() interface{} { return v.Key() } + +// Index returns the currently-selected index in the enumeration. +// The order of keys reflects the original order in which they were passed to +// the constructor, so index 0 is the default value. +func (v Value) Index() int { return v.index } + +// String satisfies part of the flag.Value interface. +func (v Value) String() string { return v.Key() } + +// Set satisfies part of the flag.Value interface. +func (v *Value) Set(s string) error { + for i, key := range v.keys { + if strings.EqualFold(s, key) { + v.index = i + return nil + } + } + return fmt.Errorf("expected one of (%s)", strings.Join(v.keys, "|")) +} + +// Type satisfies part of spf13 flag.Value interface +func (s Value) Type() string { + return "string" +} diff --git a/cmd/dkafka/parse.go b/cmd/dkafka/parse.go new file mode 100644 index 0000000..859309b --- /dev/null +++ b/cmd/dkafka/parse.go @@ -0,0 +1,88 @@ +package main + +import ( + "encoding/json" + "fmt" + + "github.com/dfuse-io/dkafka" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/streamingfast/bstream/forkable" +) + +var ParseCmd = &cobra.Command{ + Use: "parse", + Short: "Debug parsing commands", + Long: "", +} + +var ParseCursorCmd = &cobra.Command{ + Use: "cursor ", + Short: "Parse an opaque cursor", + Long: "Parse an opaque cursor and return its value", + RunE: parseCursor, +} + +var ParseAbiCmd = &cobra.Command{ + Use: "abi ", + Short: "Parse an abi hex-string to json", + Long: "Parse an hex-string abi representation and return its json form", + RunE: parseAbi, +} + +func init() { + RootCmd.AddCommand(ParseCmd) + + ParseCmd.AddCommand(ParseCursorCmd) + ParseCursorCmd.Flags().String("cursor", "", "specify the cursor with this flag when it start with a '-'") + ParseCmd.AddCommand(ParseAbiCmd) + ParseAbiCmd.Flags().String("abi", "", "specify the abi with this flag if you don't want to use the arg") +} + +func parseCursor(cmd *cobra.Command, args []string) error { + SetupLogger() + + var opaqueCursor string + if len(args) == 1 { + opaqueCursor = args[0] + } else { + opaqueCursor = viper.GetString("parse-cursor-cmd-cursor") + } + + if opaqueCursor == "" { + return fmt.Errorf("un-expected parameter only opaque cursor must be provided") + } + + cursor, err := forkable.CursorFromOpaque(opaqueCursor) + if err != nil { + return err + } + fmt.Printf("cursor: { step: %s, block: %s, LIB: %s, head: %s}\n", cursor.Step, cursor.Block, cursor.LIB, cursor.HeadBlock) + return nil +} + +func parseAbi(cmd *cobra.Command, args []string) error { + SetupLogger() + + var hexData string + if len(args) == 1 { + hexData = args[0] + } else { + hexData = viper.GetString("parse-abi-cmd-abi") + } + + if hexData == "" { + return fmt.Errorf("un-expected parameter an abi hex-string must be provided either as an arg or as a --abi option") + } + + abi, err := dkafka.DecodeABI("from-cli", "from-cli", hexData) + if err != nil { + return err + } + abiBytes, err := json.Marshal(abi) + if err != nil { + return err + } + fmt.Println(string(abiBytes)) + return nil +} diff --git a/cmd/dkafka/publish.go b/cmd/dkafka/publish.go index 1aeb320..6621721 100644 --- a/cmd/dkafka/publish.go +++ b/cmd/dkafka/publish.go @@ -2,13 +2,12 @@ package main import ( "fmt" - "strings" "time" - "github.com/streamingfast/derr" "github.com/dfuse-io/dkafka" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/streamingfast/derr" "go.uber.org/zap" ) @@ -19,46 +18,119 @@ var PublishCmd = &cobra.Command{ RunE: publishRunE, } +var compressionTypes = NewEnumFlag("none", "gzip", "snappy", "lz4", "zstd") + func init() { RootCmd.AddCommand(PublishCmd) + PublishCmd.Flags().String("kafka-cursor-topic", "_dkafka_cursors", "kafka topic where cursor will be loaded and saved") + PublishCmd.Flags().Uint32("kafka-cursor-partition", 0, "kafka partition where cursor will be loaded and saved") + PublishCmd.Flags().String("kafka-cursor-consumer-group-id", "dkafkaconsumer", "Consumer group ID for reading cursor") + PublishCmd.Flags().String("kafka-transaction-id", "dkafkatransaction", "Unique ID for transactions") + + PublishCmd.Flags().Var(compressionTypes, "kafka-compression-type", compressionTypes.Help("Specify the compression type to use for compressing message sets.")) + PublishCmd.Flags().Int8("kafka-compression-level", int8(-1), `Compression level parameter for algorithm selected by configuration property +kafka-compression-type. Higher values will result in better compression at the +cost of more CPU usage. Usable range is algorithm-dependent: [0-9] for gzip; +[0-12] for lz4; only 0 for snappy; -1 = codec-dependent default compression +level.`) + PublishCmd.Flags().Int("kafka-message-max-bytes", 1_000_000, `Maximum Kafka protocol request message size. +Due to different framing overhead between protocol versions the producer is +unable to reliably enforce a strict max message limit at produce time, +the message size is checked against your raw uncompressed message. +The broker will also enforce the the topic's max.message.bytes limit +upon receiving your message. So make sure your brokers configuration +match your producers (same apply for consumers) +(see Apache Kafka documentation).`) + PublishCmd.Flags().Duration("delay-between-commits", time.Second*10, "no commits to kafka blow this delay, except un shutdown") PublishCmd.Flags().String("event-source", "dkafka", "custom value for produced cloudevent source") - PublishCmd.Flags().String("event-keys-expr", "[account]", "CEL expression defining the event keys. More then one key will result in multiple events being sent. Must resolve to an array of strings") + PublishCmd.Flags().String("event-keys-expr", "[account]", `CEL expression defining the event keys. More then one key will result in multiple +events being sent. Must resolve to an array of strings`) PublishCmd.Flags().String("event-type-expr", "(notif?'!':'')+account+'/'+action", "CEL expression defining the event type. Must resolve to a string") + PublishCmd.Flags().String("actions-expr", "", `For that, you will use the '--actions-expr' option. + It's a JSON object that describe how each action have to be handled. + This feature allow you to fan-out multiple messages for a give action. + For example, on a NFT issue action you may want to send one message per + update of the state of the NFT factories and one message per newly + created NFTs. + The first level of properties is the name of the actions you filter in + '--dfuse-firehose-include-expr'. Then for each action you specify an array + of (one or many) projections. A projection is an JSON object who defines + an expression for the 'key' of the kafka message and the CloudEvents 'type' + (ce_type header). Those 2 properties are mandatory. Optionally, you can + specify one of the projection functions on the db_ops: '(filter|first)'. + - first: It's a single message output projection. It's configured with a single + db_op matcher. It traverses the db_ops and stop at the first matching + occurrence and return this only db_ops to build a single message. + - filter: It's a single message output projection. It's configured with a + db_op matcher. It traverses the db_ops and return the matching db_ops. + Additionally you can 'split' the resulting db_ops through the 'split' property + to send as many message as there is db_ops result. It is useful when an action + insert or update or delete multiple entries in a table and you want to emit + a message per entry like when you issue multiple NFTs in a raw and want a + message per created NTF with the associated to the id of the newly create NTF. + + A Table matcher is defined by an string expression '(:)' where: + - ":" an optional matching property. The operation string value can be + one of the following: (UNKNOWN|INSERT|UPDATE|DELETE) or a numerical positive + value between [0..3] where 0 => UNKNOWN, 1 => INSERT, 2 => UPDATE, 3 => DELETE. + You can use a special character to represent any operation => '*'. It allow + you to write a matcher like this: "*:a.factory" where any operation of the + "a.factory" table will match. + - "": a mandatory name of a given table involved into the action. + It's an exact matcher. + + Warning: this configuration option as a precedence on the '--event-type-expr' and + '--event-keys-expr' options. Therefore if specified then the 2 others are omitted + + Examples: + - simple action matching without db_ops specific projection (identity projection operator). + this is equivalent to the combined usage of --event-type-expr and --event-keys-expr: + {"create":[{"key":"transaction_id", "type":"NftFtCreatedNotification"}]} + + - first db_ops projection and db_ops property usage for key definition: + { + "create":[ + {"first": "1:factory.a", "key":"string(db_ops[0].new_json.id)", "type":"NftFtCreatedNotification"} + ] + } - PublishCmd.Flags().StringSlice("event-extensions-expr", []string{}, "cloudevent extension definitions in this format: '{key}:{CEL expression}' (ex: 'blk:string(block_num)')") + - multi actions projection: + { + "create":[ + {"first": "insert:factory.a", "key":"string(db_ops[0].new_json.id)", "type":"NftFtCreatedNotification"} + ], + "issue":[ + {"filter": "update:factory.a", "split": true, "key":"string(db_ops[0].new_json.id)", "type":"NftFtUpdatedNotification"} + ] + } + +`) PublishCmd.Flags().Bool("batch-mode", false, "Batch mode will ignore cursor and always start from {start-block-num}.") - PublishCmd.Flags().Int64("start-block-num", 0, "If we are in {batch-mode} or no prior cursor exists, start streaming from this block number (if negative, relative to HEAD)") + PublishCmd.Flags().Int64("start-block-num", 0, `If we are in {batch-mode} or no prior cursor exists, +start streaming from this block number (if negative, relative to HEAD)`) PublishCmd.Flags().Uint64("stop-block-num", 0, "If non-zero, stop processing before this block number") PublishCmd.Flags().String("state-file", "./dkafka.state.json", "progress will be saved into this file") + PublishCmd.Flags().Bool("capture", false, "Activate the capture mode where blocks are saved on the file system in json format.") - PublishCmd.Flags().StringSlice("local-abi-files", []string{}, "repeatable, ABI file definition in this format: '{account}:{path/to/filename}' (ex: 'eosio.token:/tmp/eosio_token.abi'). ABIs are used to decode DB ops. Provided ABIs have highest priority and will never be fetched or updated") + PublishCmd.Flags().StringSlice("local-abi-files", []string{}, `repeatable, ABI file definition in this format: +'{account}:{path/to/filename}' (ex: 'eosio.token:/tmp/eosio_token.abi'). +ABIs are used to decode DB ops. Provided ABIs have highest priority and +will never be fetched or updated`) PublishCmd.Flags().String("abicodec-grpc-addr", "", "if set, will connect to this endpoint to fetch contract ABIs") - PublishCmd.Flags().Bool("fail-on-undecodable-db-op", false, "If true, program will fail and exit when a db OP cannot be decoded (ex: missing or incompatible ABI file or invalid ABI fetched from abicodec") + PublishCmd.Flags().Bool("fail-on-undecodable-db-op", false, `If true, program will fail and exit when a db OP cannot be decoded +(ex: missing or incompatible ABI file or invalid ABI fetched from abicodec`) } func publishRunE(cmd *cobra.Command, args []string) error { SetupLogger() - extensions := make(map[string]string) - for _, ext := range viper.GetStringSlice("publish-cmd-event-extensions-expr") { - kv := strings.SplitN(ext, ":", 2) - if len(kv) != 2 { - return fmt.Errorf("invalid value for extension: %s", ext) - } - extensions[kv[0]] = kv[1] - } - - localABIFiles := make(map[string]string) - for _, ext := range viper.GetStringSlice("publish-cmd-local-abi-files") { - kv := strings.SplitN(ext, ":", 2) - if len(kv) != 2 { - return fmt.Errorf("invalid value for local ABI file: %s", ext) - } - localABIFiles[kv[0]] = kv[1] + localABIFiles, err := dkafka.ParseABIFileSpecs(viper.GetStringSlice("publish-cmd-local-abi-files")) + if err != nil { + return err } conf := &dkafka.Config{ @@ -74,21 +146,25 @@ func publishRunE(cmd *cobra.Command, args []string) error { KafkaSSLClientCertFile: viper.GetString("global-kafka-ssl-client-cert-file"), KafkaSSLClientKeyFile: viper.GetString("global-kafka-ssl-client-key-file"), KafkaTopic: viper.GetString("global-kafka-topic"), - KafkaCursorTopic: viper.GetString("global-kafka-cursor-topic"), - KafkaCursorPartition: int32(viper.GetUint32("global-kafka-cursor-partition")), - KafkaCursorConsumerGroupID: viper.GetString("global-kafka-cursor-consumer-group-id"), - KafkaTransactionID: viper.GetString("global-kafka-transaction-id"), + KafkaCursorTopic: viper.GetString("publish-cmd-kafka-cursor-topic"), + KafkaCursorPartition: int32(viper.GetUint32("publish-cmd-kafka-cursor-partition")), + KafkaCursorConsumerGroupID: viper.GetString("publish-cmd-kafka-cursor-consumer-group-id"), + KafkaTransactionID: viper.GetString("publish-cmd-kafka-transaction-id"), + KafkaCompressionType: viper.GetString("publish-cmd-kafka-compression-type"), + KafkaCompressionLevel: viper.GetInt("publish-cmd-kafka-compression-level"), + KafkaMessageMaxBytes: viper.GetInt("publish-cmd-kafka-message-max-bytes"), CommitMinDelay: viper.GetDuration("publish-cmd-delay-between-commits"), - EventSource: viper.GetString("publish-cmd-event-source"), - EventKeysExpr: viper.GetString("publish-cmd-event-keys-expr"), - EventTypeExpr: viper.GetString("publish-cmd-event-type-expr"), - EventExtensions: extensions, + EventSource: viper.GetString("publish-cmd-event-source"), + EventKeysExpr: viper.GetString("publish-cmd-event-keys-expr"), + EventTypeExpr: viper.GetString("publish-cmd-event-type-expr"), + ActionsExpr: viper.GetString("publish-cmd-actions-expr"), BatchMode: viper.GetBool("publish-cmd-batch-mode"), StartBlockNum: viper.GetInt64("publish-cmd-start-block-num"), StopBlockNum: viper.GetUint64("publish-cmd-stop-block-num"), StateFile: viper.GetString("publish-cmd-state-file"), + Capture: viper.GetBool("publish-cmd-capture"), LocalABIFiles: localABIFiles, ABICodecGRPCAddr: viper.GetString("publish-cmd-abicodec-grpc-addr"), diff --git a/cmd/dkafka/root.go b/cmd/dkafka/root.go index 6cf0695..5359417 100644 --- a/cmd/dkafka/root.go +++ b/cmd/dkafka/root.go @@ -38,16 +38,12 @@ func init() { RootCmd.PersistentFlags().String("kafka-ssl-client-cert-file", "./client.crt.pem", "path to client certificate to authenticate to kafka endpoint") RootCmd.PersistentFlags().String("kafka-ssl-client-key-file", "./client.key.pem", "path to client key to authenticate to kafka endpoint") - RootCmd.PersistentFlags().String("kafka-transaction-id", "dkafkatransaction", "Unique ID for transactions") - RootCmd.PersistentFlags().String("kafka-topic", "default", "kafka topic to use for all events writes or reads") - RootCmd.PersistentFlags().String("kafka-cursor-topic", "_dkafka_cursors", "kafka topic where cursor will be loaded and saved") - RootCmd.PersistentFlags().Uint32("kafka-cursor-partition", 0, "kafka partition where cursor will be loaded and saved") - RootCmd.PersistentFlags().String("kafka-cursor-consumer-group-id", "dkafkaconsumer", "Consumer group ID for reading cursor") RootCmd.PersistentFlags().String("log-format", "text", "Format for logging to stdout. Either 'text' or 'stackdriver'") RootCmd.PersistentFlags().CountP("verbose", "v", "Enables verbose output (-vvvv for max verbosity)") - RootCmd.PersistentFlags().String("log-level-switcher-listen-addr", "localhost:1065", "If non-empty, the process will listen on this address for json-formatted requests to change different logger levels (see DEBUG.md for more info)") + RootCmd.PersistentFlags().String("log-level-switcher-listen-addr", "localhost:1065", `If non-empty, the process will listen on this address for json-formatted requests +to change different logger levels (see DEBUG.md for more info)`) } func initConfig() { diff --git a/codec.go b/codec.go new file mode 100644 index 0000000..186d824 --- /dev/null +++ b/codec.go @@ -0,0 +1,147 @@ +package dkafka + +import ( + "encoding/binary" + "encoding/json" + "fmt" + "net/url" + + "github.com/confluentinc/confluent-kafka-go/kafka" + "github.com/linkedin/goavro/v2" + "github.com/riferrei/srclient" + "go.uber.org/zap" +) + +type CodecType = string + +const ( + AvroCodec CodecType = "avro" + JsonCodec CodecType = "json" + schemaByIDs = "/schemas/ids/" +) + +type Codec interface { + Marshal(buf []byte, value interface{}) ([]byte, error) + Unmarshal(buf []byte) (interface{}, error) + GetHeaders() []kafka.Header +} + +func NewJSONCodec() Codec { + return JSONCodec{} +} + +type JSONCodec struct{} + +var jsonKafkaHeader []kafka.Header = []kafka.Header{ + { + Key: "content-type", + Value: []byte("application/json"), + }, + { + Key: "ce_datacontenttype", + Value: []byte("application/json"), + }, +} + +func (c JSONCodec) Marshal(buf []byte, value interface{}) (bytes []byte, err error) { + bytes, err = json.Marshal(value) + if err != nil { + bytes = buf + } else { + bytes = append(buf, bytes...) + } + return +} + +func (c JSONCodec) Unmarshal(buf []byte) (interface{}, error) { + value := make(map[string]interface{}) + err := json.Unmarshal(buf, &value) + if err != nil { + return nil, err + } + return value, nil +} + +func (c JSONCodec) GetHeaders() []kafka.Header { + return jsonKafkaHeader +} + +func NewKafkaAvroCodec(schemaRegistryURL string, schema *srclient.Schema, codec *goavro.Codec) Codec { + u, _ := url.Parse(schemaRegistryURL) + u, _ = u.Parse(schemaByIDs) + t := fmt.Sprint(u, "%d") + return KafkaAvroCodec{ + schemaURLTemplate: t, + schema: RegisteredSchema{ + id: uint32(schema.ID()), + schema: schema.Schema(), + version: schema.Version(), + codec: codec, + }, + } +} + +type RegisteredSchema struct { + id uint32 + schema string + version int + codec *goavro.Codec `deep:"-"` +} + +type KafkaAvroCodec struct { + schemaURLTemplate string + schema RegisteredSchema +} + +func (c KafkaAvroCodec) Marshal(buf []byte, value interface{}) (bytes []byte, err error) { + zlog.Debug("marshal value to avro", zap.Uint32("schema_id", c.schema.id)) + schemaHeaderBytes := make([]byte, 5) + // append magic byte 0 + // append schema id in int32 bigendian + binary.BigEndian.PutUint32(schemaHeaderBytes[1:5], uint32(c.schema.id)) + bytes = append(bytes, schemaHeaderBytes...) + // append append value + bytes, err = c.schema.codec.BinaryFromNative(bytes, value) + if err != nil { + zlog.Debug("on error codec.BinaryFromNative()", zap.Error(err)) + bytes = buf + } else { + bytes = append(buf, bytes...) + } + return +} + +func (c KafkaAvroCodec) Unmarshal(buf []byte) (interface{}, error) { + if len(buf) < 5 { + return nil, fmt.Errorf("invalid byte buffer it must at least have a length of 5 but: %d", len(buf)) + } + // check magic byte + if buf[0] != byte(0) { + return nil, fmt.Errorf("invalid magic byte at the beginning of the buffer must be 0 but: %d", buf[0]) + } + schemaId := binary.BigEndian.Uint32(buf[1:5]) + if c.schema.id != schemaId { + return nil, fmt.Errorf("invalid magic byte at the beginning of the buffer must be %d but: %d", c.schema.id, schemaId) + } + value, _, err := c.schema.codec.NativeFromBinary(buf[5:]) + return value, err +} + +var avroKafkaHeader []kafka.Header = []kafka.Header{ + { + Key: "content-type", + Value: []byte("application/avro"), + }, + { + Key: "ce_datacontenttype", + Value: []byte("application/avro"), + }, +} + +func (c KafkaAvroCodec) GetHeaders() []kafka.Header { + u := fmt.Sprintf(c.schemaURLTemplate, c.schema.id) + return append(avroKafkaHeader, kafka.Header{ + Key: "ce_dataschema", + Value: []byte(u), + }) +} diff --git a/codec_test.go b/codec_test.go new file mode 100644 index 0000000..7e5f6e3 --- /dev/null +++ b/codec_test.go @@ -0,0 +1,850 @@ +package dkafka + +import ( + "encoding/json" + "reflect" + "testing" + + pbcodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/codec/v1" + "github.com/eoscanada/eos-go" + "github.com/linkedin/goavro/v2" + "github.com/riferrei/srclient" + "gotest.tools/assert" +) + +func TestNewJSONCode(t *testing.T) { + tests := []struct { + name string + want Codec + }{ + { + "json", + JSONCodec{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewJSONCodec(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewJSONCode() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNewKafkaAvroCodec(t *testing.T) { + schema := newSchema(t, 42, UserSchema, nil) + type args struct { + schema *srclient.Schema + } + tests := []struct { + name string + args args + want Codec + }{ + { + "kafka-avro", + args{ + schema: schema, + }, + KafkaAvroCodec{ + "mock://test/schemas/ids/%d", + RegisteredSchema{ + id: uint32(schema.ID()), + schema: schema.Schema(), + version: schema.Version(), + codec: schema.Codec(), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewKafkaAvroCodec("mock://test", tt.args.schema, tt.args.schema.Codec()); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewKafkaAvroCodec() = %v, want %v", got, tt.want) + } + }) + } +} + +const UserSchema = ` +{ + "namespace": "dkafka.test", + "name": "User", + "type": "record", + "fields": [ + { + "name": "firstName", + "type": "string" + }, + { + "name": "lastName", + "type": "string" + }, + { + "name": "middleName", + "type": [ + "null", + "string" + ], + "default": null + }, + { + "name": "age", + "type": "int" + }, + { + "name": "id", + "type": "long" + } + ] +} +` + +const TokenATableOpInfo = ` +{ + "namespace": "dkafka.test", + "type": "record", + "name": "TokenATableOpInfo", + "fields": [ + { + "name": "operation", + "type": [ + "null", + "int" + ], + "default": null + }, + { + "name": "action_index", + "type": [ + "null", + "long" + ], + "default": null + }, + { + "name": "index", + "type": "int" + }, + { + "name": "code", + "type": [ + "null", + "string" + ], + "default": null + }, + { + "name": "scope", + "type": [ + "null", + "string" + ], + "default": null + }, + { + "name": "table_name", + "type": [ + "null", + "string" + ], + "default": null + }, + { + "name": "primary_key", + "type": [ + "null", + "string" + ], + "default": null + }, + { + "name": "old_payer", + "type": [ + "null", + "string" + ], + "default": null + }, + { + "name": "new_payer", + "type": [ + "null", + "string" + ], + "default": null + }, + { + "name": "old_data", + "type": [ + "null", + "bytes" + ], + "default": null + }, + { + "name": "new_data", + "type": [ + "null", + "bytes" + ], + "default": null + }, + { + "name": "old_json", + "type": [ + "null", + { + "type": "record", + "name": "TokenATableOp", + "fields": [ + { + "name": "id", + "type": "long" + }, + { + "name": "token_factory_id", + "type": "long" + }, + { + "name": "mint_date", + "type": "string" + }, + { + "name": "serial_number", + "type": "long" + } + ] + } + ], + "default": null + }, + { + "name": "new_json", + "type": [ + "null", + "TokenATableOp" + ], + "default": null + } + ] +} +` + +func TestCodec_MarshalUnmarshal(t *testing.T) { + dbOp := &decodedDBOp{ + DBOp: &pbcodec.DBOp{ + Operation: pbcodec.DBOp_OPERATION_INSERT, + TableName: "token.a", + NewData: []byte("aAAAAAAAAAACAAAAAAAAABPuzWEJAAAA"), + }, + NewJSON: map[string]interface{}{ + "id": eos.Uint64(104), + "mint_date": "2021-12-30T17:36:19", + "serial_number": 9, + "token_factory_id": 2, + }, + } + uSchema := newSchema(t, 42, UserSchema, nil) + tSchema := newSchema(t, 42, TokenATableOpInfo, nil) + type args struct { + buf []byte + value interface{} + } + tests := []struct { + name string + c Codec + args args + wantMarshalErr bool + wantUnmarshalErr bool + expect interface{} + }{ + { + "json", + JSONCodec{}, + args{ + nil, + map[string]interface{}{ + "firstName": "Christophe", + "lastName": "O", + "age": float64(42), + }, + }, + false, + false, + nil, + }, + { + "kafka-avro", + NewKafkaAvroCodec("mock://test", uSchema, uSchema.Codec()), + args{ + nil, + map[string]interface{}{ + "firstName": "Christophe", + "lastName": "O", + "age": int32(24), + "id": int64(42), + }, + }, + false, + false, + map[string]interface{}{ + "firstName": "Christophe", + "lastName": "O", + "middleName": nil, + "age": int32(24), + "id": int64(42), + }, + }, + { + "repeated-avro", + NewKafkaAvroCodec("mock://test", tSchema, tSchema.Codec()), + args{ + nil, + dbOp.asMap("dkafka.test.TokenATableOp", 1), + }, + false, + false, + map[string]interface{}{ + "operation": map[string]interface{}{"int": int32(1)}, + "action_index": map[string]interface{}{"long": int64(0)}, + "index": int32(1), + "code": nil, + "scope": nil, + "table_name": map[string]interface{}{"string": "token.a"}, + "primary_key": nil, + "old_payer": nil, + "new_payer": nil, + "old_data": nil, + "new_data": map[string]interface{}{"bytes": []byte("aAAAAAAAAAACAAAAAAAAABPuzWEJAAAA")}, + "old_json": nil, + "new_json": map[string]interface{}{"dkafka.test.TokenATableOp": map[string]interface{}{"id": int64(104), "mint_date": "2021-12-30T17:36:19", "serial_number": int64(9), "token_factory_id": int64(2)}}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := tt.c + gotBytes, err := c.Marshal(tt.args.buf, tt.args.value) + if (err != nil) != tt.wantMarshalErr { + t.Errorf("JSONCodec.Marshal() error = %v, wantMarshalErr %v", err, tt.wantMarshalErr) + return + } + gotValue, err := c.Unmarshal(gotBytes) + if (err != nil) != tt.wantUnmarshalErr { + t.Errorf("JSONCodec.Unmarshal() error = %v, wantUnmarshalErr %v", err, tt.wantUnmarshalErr) + return + } + expect := tt.expect + if expect == nil { + expect = tt.args.value + } + if !reflect.DeepEqual(gotValue, expect) { + t.Errorf("codec Marshal() then Unmarshal() = %v, want %v", gotValue, expect) + } + }) + } +} + +func newSchema(t testing.TB, id uint32, s string, c *goavro.Codec) *srclient.Schema { + if c == nil { + c = newAvroCodec(t, s) + } + schema, err := srclient.NewSchema(42, s, srclient.Avro, 1, nil, c, nil) + if err != nil { + t.Fatalf("srclient.NewSchema() on schema: %s, error: %v", s, err) + } + return schema +} + +func newAvroCodec(t testing.TB, s string) *goavro.Codec { + // codec, err := goavro.NewCodec(s) + codec, err := goavro.NewCodecWithConverters(s, schemaTypeConverters) + if err != nil { + t.Fatalf("goavro.NewCodec() on schema: %s, error: %v", s, err) + } + return codec +} + +type TB []byte +type TString string + +var Uint8Type reflect.Type = reflect.TypeOf(uint8(0)) + +// func TestDummy(t *testing.T) { +// var value = TString("test") +// valueOf := reflect.ValueOf(value) +// switch kind := valueOf.Kind(); { +// case kind == reflect.Slice: +// fmt.Println(valueOf.Type().Elem()) +// fmt.Println(Uint8Type == valueOf.Type().Elem()) +// case kind == reflect.String: +// fmt.Println(valueOf.String()) +// } +// // fmt.Println(valueOf.Kind()) +// // fmt.Println(valueOf.Type()) +// // fmt.Println(valueOf.Type().Elem()) +// } + +func BenchmarkCodecMarshal(b *testing.B) { + user := map[string]interface{}{ + "firstName": "Chris", + "lastName": "Otto", + "middleName": "Tutu", + "age": int32(24), + "id": int64(42), + } + uSchema := newSchema(b, 42, UserSchema, nil) + tests := []struct { + name string + codec Codec + value interface{} + }{ + { + "json", + JSONCodec{}, + user, + }, + { + "avro", + NewKafkaAvroCodec("mock://test", uSchema, uSchema.Codec()), + user, + }, + } + for _, tt := range tests { + b.Run(tt.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + tt.codec.Marshal(nil, tt.value) + } + }) + } +} + +func TestAvroCodecOptionalAsset(t *testing.T) { + checkCodec(t, NewOptional(assetSchema), eos.Asset{ + Amount: eos.Int64(100_000_000), + Symbol: eos.Symbol{ + Precision: uint8(8), + Symbol: "UOS", + }, + }) +} + +func TestAvroCodecAsset(t *testing.T) { + checkCodec(t, assetSchema, eos.Asset{ + Amount: eos.Int64(100_000_000), + Symbol: eos.Symbol{ + Precision: uint8(8), + Symbol: "UOS", + }, + }) +} + +func TestAvroCodecUint128(t *testing.T) { + checkCodec(t, NewUint128Type(), eos.Uint128{ + Lo: 42, + Hi: 0, + }) +} + +func TestAvroCodecInt128(t *testing.T) { + checkCodec(t, NewInt128Type(), eos.Int128{ + Lo: 42, + Hi: 0, + }) +} + +func TestAvroCodecNullAsset(t *testing.T) { + var nullableAccountSchema = ` + { + "namespace": "dkafka.test", + "name": "Account", + "type": "record", + "fields": [ + { + "name": "balance", + "type": ["null",{ + "namespace": "eosio", + "name": "Asset", + "type": "record", + "convert": "eosio.Asset", + "fields": [ + { + "name": "amount", + "type": { + "type": "bytes", + "logicalType": "decimal", + "precision": 32, + "scale": 8 + } + }, + { + "name": "symbol", + "type": "string" + } + ] + }], + "default": null + } + ] + } + ` + + schema := newSchema(t, 42, nullableAccountSchema, nil) + + codec := KafkaAvroCodec{ + "mock://test/schemas/ids/%d", + RegisteredSchema{ + id: uint32(schema.ID()), + schema: schema.Schema(), + version: schema.Version(), + codec: schema.Codec(), + }, + } + bytes, err := codec.Marshal(nil, map[string]interface{}{ + "balance": nil, + }) + if err != nil { + t.Fatalf("codec.Marshal() error: %v", err) + } + value, err := codec.Unmarshal(bytes) + t.Logf("%v", value) + if err != nil { + t.Fatalf("codec.Unmarshal() error: %v", err) + } +} + +var AccountsTableNotificationSchema = ` +{ + "type": "record", + "name": "AccountsTableNotification", + "namespace": "test.dkafka", + "fields": [ + { + "name": "db_op", + "type": { + "type": "record", + "name": "AccountsTableOpInfo", + "fields": [ + { + "name": "old_json", + "type": [ + "null", + { + "type": "record", + "name": "AccountsTableOp", + "fields": [ + { + "name": "balance", + "type": { + "namespace": "eosio", + "name": "Asset", + "type": "record", + "convert": "eosio.Asset", + "fields": [ + { + "name": "amount", + "type": { + "type": "bytes", + "logicalType": "decimal", + "precision": 32, + "scale": 8 + } + }, + { + "name": "symbol", + "type": "string" + } + ] + } + } + ] + } + ], + "default": null + } + ] + } + }, + { + "name": "minimum_resell_price", + "type": "eosio.Asset" + } + ] +} +` + +func TestAccountsTableNotification(t *testing.T) { + + schema := newSchema(t, 42, AccountsTableNotificationSchema, nil) + + codec := KafkaAvroCodec{ + "mock://test/schemas/ids/%d", + RegisteredSchema{ + id: uint32(schema.ID()), + schema: schema.Schema(), + version: schema.Version(), + codec: schema.Codec(), + }, + } + asset := eos.Asset{ + Amount: eos.Int64(100_000_000), + Symbol: eos.Symbol{ + Precision: uint8(8), + Symbol: "UOS", + }, + } + // asset := map[string]interface{}{ + // "balance": "1.0", + // } + bytes, err := codec.Marshal(nil, map[string]interface{}{ + "db_op": map[string]interface{}{ + "old_json": map[string]interface{}{ + "balance": asset, + }, + }, + "minimum_resell_price": asset, + }) + if err != nil { + t.Fatalf("codec.Marshal() error: %v", err) + } + value, err := codec.Unmarshal(bytes) + t.Logf("%v", value) + if err != nil { + t.Fatalf("codec.Unmarshal() error: %v", err) + } +} + +var MoreAssetSchema = ` +{ + "type": "record", + "name": "AccountsTableNotification", + "namespace": "test.dkafka", + "fields": [ + { + "name": "maximum_resell_price", + "type": { + "namespace": "eosio", + "name": "Asset", + "type": "record", + "convert": "eosio.Asset", + "fields": [ + { + "name": "amount", + "type": { + "type": "bytes", + "logicalType": "decimal", + "precision": 32, + "scale": 8 + } + }, + { + "name": "symbol", + "type": "string" + } + ] + } + }, + { + "name": "minimum_resell_price", + "type": "eosio.Asset" + } + ] +} +` + +func TestMoreAsset(t *testing.T) { + + schema := newSchema(t, 42, MoreAssetSchema, nil) + + codec := KafkaAvroCodec{ + "mock://test/schemas/ids/%d", + RegisteredSchema{ + id: uint32(schema.ID()), + schema: schema.Schema(), + version: schema.Version(), + codec: schema.Codec(), + }, + } + asset := eos.Asset{ + Amount: eos.Int64(100_000_000), + Symbol: eos.Symbol{ + Precision: uint8(8), + Symbol: "UOS", + }, + } + // asset := map[string]interface{}{ + // "balance": "1.0", + // } + bytes, err := codec.Marshal(nil, map[string]interface{}{ + "maximum_resell_price": asset, + "minimum_resell_price": asset, + }) + if err != nil { + t.Fatalf("codec.Marshal() error: %v", err) + } + value, err := codec.Unmarshal(bytes) + t.Logf("%v", value) + if err != nil { + t.Fatalf("codec.Unmarshal() error: %v", err) + } +} + +func TestAvroCodecExtendedAsset(t *testing.T) { + var nullableExtendedAsset = newRecordFQN("dkafka.test", "ExtendedAccount", + []FieldSchema{ + NewOptionalField("balance", extendedAssetSchemaGenerator(map[string]string{})), + }) + + jsonSchema, err := json.Marshal(nullableExtendedAsset) + if err != nil { + t.Fatalf("cannot convert to json string the schema: %v", nullableExtendedAsset) + } + str := string(jsonSchema) + + schema := newSchema(t, 42, str, nil) + + codec := KafkaAvroCodec{ + "mock://test/schemas/ids/%d", + RegisteredSchema{ + id: uint32(schema.ID()), + schema: schema.Schema(), + version: schema.Version(), + codec: schema.Codec(), + }, + } + // null case + bytes, err := codec.Marshal(nil, map[string]any{ + "balance": nil, + }) + if err != nil { + t.Fatalf("codec.Marshal() error: %v", err) + } + value, err := codec.Unmarshal(bytes) + t.Logf("%v", value) + if err != nil { + t.Fatalf("codec.Unmarshal() error: %v", err) + } + asset := eos.Asset{ + Amount: eos.Int64(100_000_000), + Symbol: eos.Symbol{ + Precision: uint8(8), + Symbol: "UOS", + }, + } + extendedAsset := eos.ExtendedAsset{ + Asset: asset, + Contract: "eosio.token", + } + // non-null case + bytes, err = codec.Marshal(nil, map[string]any{ + "balance": extendedAsset, + }) + if err != nil { + t.Fatalf("codec.Marshal() error: %v", err) + } + value, err = codec.Unmarshal(bytes) + t.Logf("%v", value) + if err != nil { + t.Fatalf("codec.Unmarshal() error: %v", err) + } +} + +func TestAvroCodecSymbol(t *testing.T) { + checkCodec(t, NewSymbolType(), eos.Symbol{ + Precision: uint8(8), + Symbol: "UOS", + }) +} + +var TestSymbolSchema = ` +{ + "type": "record", + "name": "TestSymbol", + "namespace": "test.dkafka", + "fields": [ + {"name":"block_id","type":"string"}, + {"name": "symbol", "type":{"convert":"eos.Symbol","type":"string"}} + ] +} +` + +func TestAvroCodecSymbolWithString(t *testing.T) { + + schema := newSchema(t, 42, TestSymbolSchema, nil) + + codec := KafkaAvroCodec{ + "mock://test/schemas/ids/%d", + RegisteredSchema{ + id: uint32(schema.ID()), + schema: schema.Schema(), + version: schema.Version(), + codec: schema.Codec(), + }, + } + bytes, err := codec.Marshal(nil, map[string]interface{}{ + "block_id": "abc", + "symbol": eos.Symbol{Precision: uint8(8), + Symbol: "UOS", + }, + }) + if err != nil { + t.Fatalf("codec.Marshal() error: %v", err) + } + value, err := codec.Unmarshal(bytes) + if err != nil { + t.Fatalf("codec.Unmarshal() error: %v", err) + } + actual := value.(map[string]interface{}) + t.Logf("%v", value) + assert.Equal(t, actual["block_id"], "abc") + +} + +func checkCodec[T any](t *testing.T, fieldType interface{}, value T) { + schema := newRecordFQN( + "dkafka.test", + "TestCodec", + []FieldSchema{ + { + Name: "value", + Type: fieldType, + }, + }) + schemaV := newSchema(t, 42, marshalSchema(t, schema), nil) + + codec := KafkaAvroCodec{ + "mock://test/schemas/ids/%d", + RegisteredSchema{ + id: uint32(schemaV.ID()), + schema: schemaV.Schema(), + version: schemaV.Version(), + codec: schemaV.Codec(), + }, + } + checkCodecInstance(t, codec, value) + checkCodecInstance(t, codec, &value) +} + +func checkCodecInstance[T any](t *testing.T, codec KafkaAvroCodec, value T) { + bytes, err := codec.Marshal(nil, map[string]interface{}{ + "value": value, + }) + if err != nil { + t.Fatalf("codec.Marshal() error: %v", err) + } + v, err := codec.Unmarshal(bytes) + t.Logf("checkCodecInstance unmarshal value: %v", v) + if err != nil { + t.Fatalf("codec.Unmarshal() error: %v", err) + } +} + +func marshalSchema(t *testing.T, schema Schema) string { + bytes, err := json.Marshal(schema) + if err != nil { + t.Fatalf("cannot convert to json string the schema: %v", schema) + return "" + } + return string(bytes) +} diff --git a/cursor_test.go b/cursor_test.go new file mode 100644 index 0000000..35c8ae7 --- /dev/null +++ b/cursor_test.go @@ -0,0 +1,20 @@ +package dkafka + +import ( + "testing" + + "github.com/streamingfast/opaque" +) + +func TestOpaque(t *testing.T) { + // 2Yc5z3OwLqqIBgaffIdxcve7LJY_DFA9UgnhKkQS0N6ipXc= => "1:807::b7240eceede5" + cursor := "2Yc5z3OwLqqIBgaffIdxcve7LJY_DFA9UgnhKkQS0N6ipXc=" + out, err := opaque.FromOpaque(cursor) + if err != nil { + t.Errorf("opaque.FromOpaque: %v", err) + } + dout, _ := opaque.DecodeToString(cursor) + if out != dout { + t.Errorf("out: %s != dout: %s", out, dout) + } +} diff --git a/debugger.go b/debugger.go deleted file mode 100644 index bd409b1..0000000 --- a/debugger.go +++ /dev/null @@ -1,186 +0,0 @@ -package dkafka - -import ( - "context" - "fmt" - - "github.com/confluentinc/confluent-kafka-go/kafka" - "github.com/streamingfast/bstream/forkable" - "go.uber.org/zap" -) - -type Debugger struct { - config *Config -} - -func NewDebugger(config *Config) *Debugger { - return &Debugger{ - config: config, - } -} - -func (d *Debugger) ReadCursor() error { - conf := createKafkaConfig(d.config) - - producer, err := getKafkaProducer(conf, "") - if err != nil { - return fmt.Errorf("getting kafka producer: %w", err) - } - - cp := newKafkaCheckpointer(conf, d.config.KafkaCursorTopic, d.config.KafkaCursorPartition, d.config.KafkaTopic, d.config.KafkaCursorConsumerGroupID, producer) - - cursor, err := cp.Load() - if err != nil { - return err - } - fmt.Println("cursor is", cursor) - if cursor != "" { - c, err := forkable.CursorFromOpaque(cursor) - if err != nil { - return err - } - zlog.Info("running in live mode, found cursor", - zap.String("cursor", cursor), - zap.Stringer("plain_cursor", c), - zap.Stringer("cursor_block", c.Block), - zap.Stringer("cursor_head_block", c.HeadBlock), - zap.Stringer("cursor_LIB", c.LIB), - ) - fmt.Printf("%+v\n", c) - } - return nil -} - -func (d *Debugger) WriteCursor(cursor string) error { - if cursor == "" { - return d.DeleteCursor() - } - - conf := createKafkaConfig(d.config) - - producer, err := getKafkaProducer(conf, "") - if err != nil { - return fmt.Errorf("getting kafka producer: %w", err) - } - - if c, err := forkable.CursorFromString(cursor); err == nil { - cursor = c.ToOpaque() // we always write opaque version - } - if _, err = forkable.CursorFromOpaque(cursor); err != nil { - return fmt.Errorf("invalid cursor: %s", cursor) - } - - cp := newKafkaCheckpointer(conf, d.config.KafkaCursorTopic, d.config.KafkaCursorPartition, d.config.KafkaTopic, d.config.KafkaCursorConsumerGroupID, producer) - - err = cp.Save(cursor) - if err != nil { - return err - } - fmt.Println("successfully set cursor to", cursor) - producer.Close() - return nil -} - -func (d *Debugger) DeleteCursor() error { - conf := createKafkaConfig(d.config) - - producer, err := getKafkaProducer(conf, "") - if err != nil { - return fmt.Errorf("getting kafka producer: %w", err) - } - - cp := newKafkaCheckpointer(conf, d.config.KafkaCursorTopic, d.config.KafkaCursorPartition, d.config.KafkaTopic, d.config.KafkaCursorConsumerGroupID, producer) - - err = cp.Save("") - if err != nil { - return err - } - fmt.Println("successfully set empty cursor") - producer.Close() - return nil -} - -func (d *Debugger) Write(key, val string) error { - conf := createKafkaConfig(d.config) - producer, err := getKafkaProducer(conf, d.config.KafkaTransactionID) - if err != nil { - return fmt.Errorf("getting kafka producer: %w", err) - } - - s, err := getKafkaSender(producer, &nilCheckpointer{}, d.config.KafkaTransactionID != "") - if err != nil { - return err - } - - msg := kafka.Message{ - Key: []byte(key), - Value: []byte(val), - TopicPartition: kafka.TopicPartition{ - Topic: &d.config.KafkaTopic, - Partition: kafka.PartitionAny, - }, - } - fmt.Printf("sending message: %s:%s to topic %s\n", key, val, d.config.KafkaTopic) - if err := s.Send(&msg); err != nil { - return fmt.Errorf("sending message: %w", err) - } - - if err := s.Commit(context.Background(), ""); err != nil { - return fmt.Errorf("committing message: %w", err) - } - s.producer.Close() - return nil -} - -func (d *Debugger) Read(groupID string, numValues int, startOffset int) error { - conf := createKafkaConfig(d.config) - - conf["group.id"] = groupID - //conf["auto.offset.reset"] = "smallest" - consumer, err := kafka.NewConsumer(&conf) - if err != nil { - return fmt.Errorf("creating consumer: %w", err) - } - - defer func() { - if err := consumer.Unsubscribe(); err != nil { - zlog.Error("error unsubscribing consumer", zap.Error(err)) - } - if err := consumer.Close(); err != nil { - zlog.Error("error closing consumer", zap.Error(err)) - } - }() - - consumer.Subscribe(d.config.KafkaTopic, nil) - - if startOffset >= 0 { - zlog.Debug("setting offset to..", zap.Int("start_offset", startOffset)) - err = consumer.Assign([]kafka.TopicPartition{ - kafka.TopicPartition{ - Topic: &d.config.KafkaTopic, - Offset: kafka.Offset(startOffset), - }}) - if err != nil { - return fmt.Errorf("assigning topic and offset: %w", err) - } - } - - for i := 0; numValues <= 0 || i < numValues; i++ { - ev := consumer.Poll(1000) - switch event := ev.(type) { - case kafka.Error: - fmt.Printf("got error: %s\n", event) - return event - case *kafka.Message: - fmt.Printf("got event: %+v, key:%s, val:%s (partition: %d)\n", event, string(event.Key), string(event.Value), event.TopicPartition.Partition) - default: - if ev == nil { - fmt.Println("got nil value") - } else { - fmt.Println("got unexpected value", ev) - } - } - } - - return nil -} diff --git a/doc/action-tree.md b/doc/action-tree.md new file mode 100644 index 0000000..2b98466 --- /dev/null +++ b/doc/action-tree.md @@ -0,0 +1,49 @@ +# Action Tree + +A single transaction can contain multiple actions of the same type, such as `buy`. Since an action can itself be a group of sub-actions, we need an effective way to associate these sub-actions with their parent actions. + +For example, if you examine a transaction like `6a275ebf480a8c17b76ad8df25a5a84bf65099b8f1d203dc98604760ebb9bde8` in the [Ultra Explorer](https://explorer.mainnet.ultra.io/tx/6a275ebf480a8c17b76ad8df25a5a84bf65099b8f1d203dc98604760ebb9bde8), you can see that DFuse generates a hierarchical tree structure for actions: + +![Transaction Action Tree](action-tree.png) + +To create this tree structure, we rely on four fields: + +### Action Fields + +- **`actionOrdinal`**: The unique identifier for each action within the transaction (in incremental order). +- **`creatorActionOrdinal`**: The unique identifier of the parent action. +- **`closestUnnotifiedAncestorActionOrdinal`**: (Further details needed; currently unclear.) +- **`executionIndex`**: Provides the order in which actions are executed within the transaction (in incremental order). + +For instance, to determine that the second `ultra.avatar` action is linked to the first action, we refer to the `creatorActionOrdinal` value, which is set to `1`. + +### JSON Example + +Here’s a sample of an `actionTraces` JSON object: + +``` +"actionTraces": [ + { + "actionOrdinal": 1, + "executionIndex": 0 + }, + { + "actionOrdinal": 3, + "creatorActionOrdinal": 1, + "closestUnnotifiedAncestorActionOrdinal": 1, + "executionIndex": 1 + }, + { + "actionOrdinal": 7, + "creatorActionOrdinal": 1, + "closestUnnotifiedAncestorActionOrdinal": 1, + "executionIndex": 5 + }, + { + "actionOrdinal": 11, + "creatorActionOrdinal": 7, + "closestUnnotifiedAncestorActionOrdinal": 7, + "executionIndex": 6 + } +] +``` diff --git a/doc/action-tree.png b/doc/action-tree.png new file mode 100644 index 0000000..9d8d37e Binary files /dev/null and b/doc/action-tree.png differ diff --git a/doc/capture-block.md b/doc/capture-block.md new file mode 100644 index 0000000..0172d9e --- /dev/null +++ b/doc/capture-block.md @@ -0,0 +1,47 @@ +# Capturing a Block to Create a Test + +In the case of an issue with a specific block (e.g., serialization problems), you can easily capture the corresponding block to build a local test case. + +## Step 1: Identify the Block Number +You can find the corresponding block number in the error log. It will look something like this: + +``` +Error: transform to kafka message at block_num: 135283216, cursor: W5ph5hsUAYaSABpDgP3vVKWwLpcyB1lqVwPmKBBHj4v-8XLB35ymAzQjYB2Bwvqi2hHqHV3-2d-bF3cqoMhYv9jjkbFtvig-F3IklNvqqrTmeqGmbQgcJLwxDO7dZNHRWj_SYAL4e7cJ6tXvO_PdZhczYMFyLmPmi24C9NFWeKIT7HVgxDWsdprW1f-WpNYVrrdzEbL0xyvyA2F4fh1eNcvXY6OWuT52ZiE=, , cannot encode binary record "io.dkafka.eosio.nft.ft.v6.tables.FactoryBTableNotification" field "db_op": value does not match its schema: cannot encode binary record "io.dkafka.eosio.nft.ft.v6.tables.FactoryBTableOpInfo" field "new_json": value does not match its schema: cannot encode binary record "io.dkafka.eosio.nft.ft.v6.tables.FactoryBTableOp" field "keys": value does not match its schema: cannot encode binary record "io.dkafka.eosio.nft.ft.v6.tables.FactoryKeys" field "key_defs": value does not match its schema: cannot encode binary array item 1: map[default_value:[uint32 0] edit_rights:7 editors:[ultra.prop1] name:Gold type_index:6]: cannot encode binary record "io.dkafka.eosio.nft.ft.v6.tables.KeyDefTable" field "default_value": value does not match its schema: cannot encode binary union: non-nil Union values ought to be specified with Go map[string]interface{}, with single key equal to type name, and value equal to datum value: [null int long float double string bytes boolean array]; received: []interface {} +``` + +In this case, the block number is `135283216`. + +## Step 2: Identify the Corresponding Smart Contract +You can identify the relevant smart contract by checking the topic name, which usually contains the contract name, or by searching for certain field names in your ABI files. + +In this example, you can determine the smart contract is `eosio.nft.ft` based on the topic name: + +``` +io.dkafka.eosio.nft.ft.v6.tables +``` + +## Step 3: Start dkafka Locally +To start `dkafka` locally, you need to complete the following steps: + +1. Set up your `KUBECONFIG` and `ENV` environment variables to access the corresponding environment that provides the `dfuse` firehose and `abicodec`. (At Ultra, this is available in the Dfuse Kubernetes environment.) +2. Start the local Kafka cluster by running `make up`. +3. Forward the dfuse port by running `make forward`. +4. Start the CDC capture for the given block and smart contract by running: + + ``` + make cdc-tables CDC_START_BLOCK=135283216 CDC_ACCOUNT=eosio.nft.ft + ``` + +This will generate a file in the project root with a name like `block-135283216.pb.json`. + +Move this file into the `testdata/` folder along with the corresponding ABI file. + +## Step 4: Stop dkafka +To stop the local setup, run the following commands: + +``` +make down forward-stop +``` + +## Step 5: Build Your Test +Look for existing tests that use blocks and add your test scenario accordingly. diff --git a/doc/estimate-message-size.md b/doc/estimate-message-size.md new file mode 100644 index 0000000..fb50a09 --- /dev/null +++ b/doc/estimate-message-size.md @@ -0,0 +1,11 @@ +# Estimate a message size in Kafka + +Sometimes Blockchain team ask us how many elements they can add into their event to not reach the size limit. + +So we can download the block; manuellay multiply the number of elements and print the number of elements of the Kafka values from a test. + +```golang +fmt.Printf("messages size: %v\n", len(messages[0].Value)) +``` + +1 000 wallet ~= 138.353515625 KB \ No newline at end of file diff --git a/doc/types/hardcoded-variant-types.md b/doc/types/hardcoded-variant-types.md new file mode 100644 index 0000000..76e501c --- /dev/null +++ b/doc/types/hardcoded-variant-types.md @@ -0,0 +1,14 @@ +# Hardcoded variant types + +## Uber variant type + +To provide "dynamic" data per Uniq, blockchain introduces a variant type that includes almost all existing types (primitives and arrays). + +There are two constraints on the Avro side: + +* Adding a new type to an existing Avro union is a breaking change, so we must implement a solution that ideally will never need to change (although never say never...). +* We cannot manage logical types because only one instance of a type (int, double, etc.) can exist in the union. + +To simplify things, we decided to hardcode the "uber" variant type into an Avro schema that contains all possible primitive types and arrays. + +The only downside is that this approach relies on looking up the variant name within the type. Therefore, if the type changes, the code will need to be updated accordingly. \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2db3cdb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,52 @@ +services: + redpanda: + # NOTE: Please use the latest version here! + image: docker.redpanda.com/vectorized/redpanda:v23.1.7 + container_name: redpanda-1 + command: + - redpanda start + - --smp 1 + - --overprovisioned + - --reserve-memory 0M + - --check=false + - --unsafe-bypass-fsync + - --kafka-addr PLAINTEXT://0.0.0.0:29092,OUTSIDE://0.0.0.0:9092 + - --advertise-kafka-addr PLAINTEXT://redpanda:29092,OUTSIDE://localhost:9092 + - --pandaproxy-addr 0.0.0.0:8082 + - --advertise-pandaproxy-addr localhost:8082 + ports: + - 8081:8081 + - 8082:8082 + - 9092:9092 + - 28082:28082 + - 29092:29092 + kowl: + image: docker.redpanda.com/redpandadata/console:latest + depends_on: + - redpanda + restart: always + environment: + KAFKA_BROKERS: redpanda:29092 + KAFKA_SCHEMAREGISTRY_ENABLED: 'true' + KAFKA_SCHEMAREGISTRY_URLS: "http://redpanda:8081" + ports: + - "8080:8080" + # akhq: + # # build: + # # context: . + # image: tchiotludo/akhq + # environment: + # AKHQ_CONFIGURATION: | + # akhq: + # connections: + # docker-kafka-server: + # properties: + # bootstrap.servers: "redpanda:29092" + # schema-registry: + # url: "http://redpanda:8081" + + # ports: + # - 8090:8080 + # links: + # - redpanda + diff --git a/error.log b/error.log new file mode 100644 index 0000000..6533bff --- /dev/null +++ b/error.log @@ -0,0 +1,34 @@ +{"severity":"INFO","timestamp":"2025-06-11T08:25:38.75494888Z","logger":"common","caller":"dkafka/cdc.go:274","message":"starting dkafka publisher","config":{"DfuseGRPCEndpoint":"firehose:9000","DfuseToken":"","DryRun":false,"BatchMode":false,"Capture":false,"StartBlockNum":239065644,"StopBlockNum":0,"StateFile":"","Force":false,"KafkaEndpoints":"public-staging-ultraio-kafka-staging.aivencloud.com:21821","KafkaSSLEnable":true,"KafkaSSLCAFile":"/etc/opt/dkafka/ca.pem","KafkaSSLAuth":true,"KafkaSSLClientCertFile":"/etc/opt/dkafka/client.crt.pem","KafkaSSLClientKeyFile":"/etc/opt/dkafka/client.key.pem","KafkaCompressionType":"none","KafkaCompressionLevel":-1,"KafkaMessageMaxBytes":1000000,"KafkaCursorConsumerGroupID":"dkafkaconsumer","KafkaTransactionID":"","CommitMinDelay":600000000000,"KafkaTopic":"io.dkafka.data.aom.mining.actions","KafkaCursorTopic":"","KafkaCursorPartition":0,"EventSource":"","IncludeFilterExpr":"executed","EventKeysExpr":"","EventTypeExpr":"","ActionsExpr":"","LocalABIFiles":{},"ABICodecGRPCAddr":"dns:///abicodec-v3:9000","FailOnUndecodableDBOP":false,"CdCType":"actions","Account":"aom.mining","ActionExpressions":"{\"*\":\"transaction_id\"}","TableNames":null,"Executed":true,"Irreversible":true,"SkipDbOps":false,"Codec":"avro","SchemaRegistryURL":"https://staging.api.ultra.io/kafka-schema-registry","SchemaNamespace":"io.dkafka.data","SchemaMajorVersion":2,"SchemaVersion":""},"logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/cmd/dkafka/cdc.go","line":"274","function":"main.executeCdC"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:38.75553968Z","logger":"common","caller":"app/app.go:159","message":"event source","ce_source":"dkafka-data-aom-mining-actions-59f59c6749-n7jh9","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/app.go","line":"159","function":"github.com/dfuse-io/dkafka.(*App).Run"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:38.75548419Z","logger":"common","caller":"app/metrics.go:32","message":"Starting prometheus HTTP server","listen_addr":":9102","path":"/metrics","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/metrics.go","line":"32","function":"github.com/dfuse-io/dkafka.startPrometheusMetrics"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:38.77514443Z","logger":"common","caller":"app/app.go:235","message":"setting up ABIDecoder","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/app.go","line":"235","function":"github.com/dfuse-io/dkafka.(*App).Run"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:38.77532026Z","logger":"common","caller":"app/app.go:522","message":"try to find previous position from message topic","topic":"io.dkafka.data.aom.mining.actions","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/app.go","line":"522","function":"github.com/dfuse-io/dkafka.(*App).loadCursor"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:38.81921446Z","logger":"common","caller":"app/brequest.go:128","message":"retrieve cursor header from kafka message","topic":"io.dkafka.data.aom.mining.actions","partition":0,"offset":12,"logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/brequest.go","line":"128","function":"github.com/dfuse-io/dkafka.getHeadCursorFromPartition"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:38.820374989Z","logger":"common","caller":"app/brequest.go:106","message":"compare cursor position to find the latest one","topic":"io.dkafka.data.aom.mining.actions","partition":0,"current_position":{},"latest_position":{},"logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/brequest.go","line":"106","function":"github.com/dfuse-io/dkafka.LoadCursor"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:38.832974969Z","logger":"common","caller":"app/brequest.go:128","message":"retrieve cursor header from kafka message","topic":"io.dkafka.data.aom.mining.actions","partition":1,"offset":1,"logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/brequest.go","line":"128","function":"github.com/dfuse-io/dkafka.getHeadCursorFromPartition"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:38.834194919Z","logger":"common","caller":"app/brequest.go:106","message":"compare cursor position to find the latest one","topic":"io.dkafka.data.aom.mining.actions","partition":1,"current_position":{},"latest_position":{},"logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/brequest.go","line":"106","function":"github.com/dfuse-io/dkafka.LoadCursor"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:38.848350929Z","logger":"common","caller":"app/brequest.go:128","message":"retrieve cursor header from kafka message","topic":"io.dkafka.data.aom.mining.actions","partition":2,"offset":1,"logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/brequest.go","line":"128","function":"github.com/dfuse-io/dkafka.getHeadCursorFromPartition"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:38.849441879Z","logger":"common","caller":"app/brequest.go:106","message":"compare cursor position to find the latest one","topic":"io.dkafka.data.aom.mining.actions","partition":2,"current_position":{},"latest_position":{},"logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/brequest.go","line":"106","function":"github.com/dfuse-io/dkafka.LoadCursor"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:51.826548922Z","logger":"common","caller":"app/app.go:535","message":"no cursor topic specified skip loading position from it","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/app.go","line":"535","function":"github.com/dfuse-io/dkafka.(*App).loadCursor"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:51.832762393Z","logger":"common","caller":"app/streamedabicodec.go:279","message":"reset schema cache on reload static schema","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/streamedabicodec.go","line":"279","function":"github.com/dfuse-io/dkafka.(*StreamedAbiCodec).resetCodecs"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:51.832826193Z","logger":"common","caller":"app/streamedabicodec.go:285","message":"register static schema","index":0,"name":"DKafkaCheckpoint","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/streamedabicodec.go","line":"285","function":"github.com/dfuse-io/dkafka.(*StreamedAbiCodec).initStaticSchemas"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:52.03497025Z","logger":"common","caller":"app/app.go:268","message":"Filter blocks","request":"start_block_num:239065644 start_cursor:\"WuGiAeYVNxWmszhXhimRn6WzLpc-DFhsXAnkKxlJhYH2pXbH3JigCDV1OUzXlKDwiUC-Hlyq3tnMHX568MJRudi-xeo3vHJsQSl-x4y9qLXrfvDxOAscIr40XOzdNNvfUT7Uawj6e7gH4N_nb_fbZkdhYJMvf2_i2zlSo4VScKNF6HcyxDT9esvUha2R-YBIquEhF7HzxyOkUjUvfxtcaZrRY_POvG4jYCFu\" fork_steps:STEP_IRREVERSIBLE include_filter_expr:\"executed && (account==\\\"aom.mining\\\" && receiver==\\\"aom.mining\\\") || (action==\\\"setabi\\\" && account==\\\"eosio\\\" && data.account==\\\"aom.mining\\\")\"","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/app.go","line":"268","function":"github.com/dfuse-io/dkafka.(*App).Run"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:52.05836146Z","logger":"common","caller":"app/app.go:615","message":"Start looping over blocks...","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/app.go","line":"615","function":"github.com/dfuse-io/dkafka.iterate"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:52.148765429Z","logger":"common","caller":"app/streamedabicodec.go:52","message":"new ABI loaded","contract":"aom.mining","block_num":239211881,"abi_block_num":239125097,"logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/streamedabicodec.go","line":"52","function":"github.com/dfuse-io/dkafka.(*DfuseAbiRepository).GetAbi"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:52.436243075Z","logger":"common","caller":"app/adapt.go:76","message":"incoming block 1/1000","block_num":239212000,"step":"IRREVERSIBLE","length_filtered_trx_traces":0,"logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/adapt.go","line":"76","function":"github.com/dfuse-io/dkafka.(*CdCAdapter).Adapt"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:54.408799392Z","logger":"common","caller":"app/adapt.go:76","message":"incoming block 1/1000","block_num":239213000,"step":"IRREVERSIBLE","length_filtered_trx_traces":0,"logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/adapt.go","line":"76","function":"github.com/dfuse-io/dkafka.(*CdCAdapter).Adapt"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:56.392521687Z","logger":"common","caller":"app/adapt.go:76","message":"incoming block 1/1000","block_num":239214000,"step":"IRREVERSIBLE","length_filtered_trx_traces":0,"logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/adapt.go","line":"76","function":"github.com/dfuse-io/dkafka.(*CdCAdapter).Adapt"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:58.367581644Z","logger":"common","caller":"app/gen.go:369","message":"new abi published defer clear ABI cache at end of this block parsing","block_num":239214999,"trx_index":1,"trx_id":"de7131d2f5a889cb26c045fd2d927453d125901f520f480177b0baadba85dd83","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/gen.go","line":"369","function":"github.com/dfuse-io/dkafka.transaction2ActionsGenerator.Apply"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:58.367689373Z","logger":"common","caller":"app/streamedabicodec.go:215","message":"update abi","block_num":239214999,"transaction_id":"de7131d2f5a889cb26c045fd2d927453d125901f520f480177b0baadba85dd83","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/streamedabicodec.go","line":"215","function":"github.com/dfuse-io/dkafka.(*StreamedAbiCodec).UpdateABI"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:58.367990894Z","logger":"common","caller":"app/streamedabicodec.go:279","message":"reset schema cache on reload static schema","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/streamedabicodec.go","line":"279","function":"github.com/dfuse-io/dkafka.(*StreamedAbiCodec).resetCodecs"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:58.368018194Z","logger":"common","caller":"app/streamedabicodec.go:285","message":"register static schema","index":0,"name":"DKafkaCheckpoint","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/streamedabicodec.go","line":"285","function":"github.com/dfuse-io/dkafka.(*StreamedAbiCodec).initStaticSchemas"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:58.449022162Z","logger":"common","caller":"app/adapt.go:76","message":"incoming block 1/1000","block_num":239215000,"step":"IRREVERSIBLE","length_filtered_trx_traces":0,"logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/adapt.go","line":"76","function":"github.com/dfuse-io/dkafka.(*CdCAdapter).Adapt"},"serviceContext":{"service":"common"}} +{"severity":"ERROR","timestamp":"2025-06-11T08:25:58.65951119Z","logger":"common","caller":"app/app.go:639","message":"exit block streaming on error","error":"transform to kafka message at block_num: 239215080, cursor: kj3nvbrOJx51pyFN8z9E_qWzLpc-DFhsXAnkLxFJhYH2pXbH35zzCGF0ak6CxKrxiEC4Tlj-3trPEi94o8BTtYLpkrk1uyBqRi5_wIy7qOXnK_f0PQoYJO83WurbNInbUT7Uawj6f7AH5N_nb_fbZUA2ZJV3LmC3jDtYpNZQdKcX6HdklTX8c5nUharH94JE-uUmROGmnCijATN6JBtZNcSGYaGXuDQnMCRr, , StreamedAbiCodec.newCodec fail to create codec for schema UpdrsrcsttngActionNotification, error: CreateSchema on subject: 'io.dkafka.data.aom.mining.actions.v2.UpdrsrcsttngActionNotification', schema:\n{\"type\":\"record\",\"name\":\"UpdrsrcsttngActionNotification\",\"namespace\":\"io.dkafka.data.aom.mining.actions.v2\",\"fields\":[{\"name\":\"context\",\"type\":{\"type\":\"record\",\"name\":\"NotificationContext\",\"namespace\":\"io.dkafka\",\"fields\":[{\"name\":\"block_num\",\"type\":\"long\"},{\"name\":\"block_id\",\"type\":\"string\"},{\"name\":\"status\",\"type\":\"string\"},{\"name\":\"executed\",\"type\":\"boolean\"},{\"name\":\"block_step\",\"type\":\"string\"},{\"name\":\"correlation\",\"type\":[\"null\",{\"type\":\"record\",\"name\":\"Correlation\",\"namespace\":\"io.dkafka\",\"fields\":[{\"name\":\"payer\",\"type\":\"string\"},{\"name\":\"id\",\"type\":\"string\"}]}],\"default\":null},{\"name\":\"trx_id\",\"type\":\"string\"},{\"name\":\"time\",\"type\":{\"eos.type\":\"block_timestamp_type\",\"logicalType\":\"timestamp-millis\",\"type\":\"long\"}},{\"name\":\"cursor\",\"type\":\"string\"}]}},{\"name\":\"act_info\",\"type\":{\"type\":\"record\",\"name\":\"UpdrsrcsttngActionInfo\",\"fields\":[{\"name\":\"account\",\"type\":\"string\"},{\"name\":\"receiver\",\"type\":\"string\"},{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"global_seq\",\"type\":\"long\"},{\"name\":\"authorizations\",\"type\":{\"type\":\"array\",\"items\":\"string\"}},{\"name\":\"action_ordinal\",\"type\":\"long\"},{\"name\":\"creator_action_ordinal\",\"type\":\"long\"},{\"name\":\"closest_unnotified_ancestor_action_ordinal\",\"type\":\"long\"},{\"name\":\"execution_index\",\"type\":\"long\"},{\"name\":\"json_data\",\"type\":{\"type\":\"record\",\"name\":\"UpdrsrcsttngActionParams\",\"fields\":[{\"name\":\"row\",\"type\":{\"type\":\"record\",\"name\":\"MiningResourceSettings\",\"fields\":[{\"name\":\"resource_symbol\",\"type\":{\"eos.type\":\"symbol\",\"type\":\"string\"}},{\"name\":\"resource_type\",\"type\":{\"eos.type\":\"string\",\"type\":\"string\"}},{\"name\":\"to_be_prospected\",\"type\":{\"eos.type\":\"uint64\",\"logicalType\":\"eos.uint64\",\"type\":\"long\"}},{\"name\":\"min_droprate_qty\",\"type\":{\"eos.type\":\"uint64\",\"logicalType\":\"eos.uint64\",\"type\":\"long\"}},{\"name\":\"max_droprate_qty\",\"type\":{\"eos.type\":\"uint64\",\"logicalType\":\"eos.uint64\",\"type\":\"long\"}},{\"name\":\"base_daily_mining_qty\",\"type\":{\"eos.type\":\"uint64\",\"logicalType\":\"eos.uint64\",\"type\":\"long\"}},{\"name\":\"base_storage_qty\",\"type\":{\"eos.type\":\"uint64\",\"logicalType\":\"eos.uint64\",\"type\":\"long\"}},{\"name\":\"max_storage_qty\",\"type\":{\"eos.type\":\"uint64\",\"logicalType\":\"eos.uint64\",\"type\":\"long\"}},{\"name\":\"production_speed_upgrade_increment\",\"type\":{\"eos.type\":\"float64\",\"type\":\"double\"}},{\"name\":\"max_production_speed\",\"type\":{\"eos.type\":\"float64\",\"type\":\"double\"}}]}}]}},{\"name\":\"db_ops\",\"type\":{\"type\":\"array\",\"items\":{\"type\":\"record\",\"name\":\"DBOpBasic\",\"namespace\":\"io.dkafka\",\"fields\":[{\"name\":\"operation\",\"type\":[\"null\",\"int\"],\"default\":null},{\"name\":\"action_index\",\"type\":[\"null\",\"long\"],\"default\":null},{\"name\":\"index\",\"type\":\"int\"},{\"name\":\"code\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"scope\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"table_name\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"primary_key\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"old_payer\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"new_payer\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"old_data\",\"type\":[\"null\",\"bytes\"],\"default\":null},{\"name\":\"new_data\",\"type\":[\"null\",\"bytes\"],\"default\":null}]}}}]}}],\"meta\":{\"compatibility\":\"FORWARD\",\"type\":\"notification\",\"version\":\"2.239214999.0\",\"source\":\"dkafka-data-aom-mining-actions-59f59c6749-n7jh9\",\"domain\":\"aom.mining\"}} error: {\"error_code\":409,\"message\":\"Schema being registered is incompatible with an earlier schema for subject \\\"io.dkafka.data.aom.mining.actions.v2.UpdrsrcsttngActionNotification\\\"; error code: 409\"}","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/app.go","line":"639","function":"github.com/dfuse-io/dkafka.iterate"},"serviceContext":{"service":"common"},"context":{"reportLocation":{"filePath":"/app/app.go","lineNumber":"639","functionName":"github.com/dfuse-io/dkafka.iterate"}}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:58.65966614Z","logger":"common","caller":"app/app.go:626","message":"stop ticker","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/app.go","line":"626","function":"github.com/dfuse-io/dkafka.iterate.func2"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:58.65968743Z","logger":"common","caller":"app/app.go:619","message":"close block input channel","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/app.go","line":"619","function":"github.com/dfuse-io/dkafka.iterate.func1"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:58.65970334Z","logger":"common","caller":"app/app.go:229","message":"close kafka producer","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/app.go","line":"229","function":"github.com/dfuse-io/dkafka.(*App).Run.func4"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:58.65972364Z","logger":"common","caller":"app/app.go:674","message":"incoming block channel is closed exit 'blockHandler' goroutine","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/app.go","line":"674","function":"github.com/dfuse-io/dkafka.blockHandler"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:58.67948994Z","logger":"common","caller":"app/app.go:180","message":"close error channel","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/app.go","line":"180","function":"github.com/dfuse-io/dkafka.(*App).Run.func2"},"serviceContext":{"service":"common"}} +{"severity":"INFO","timestamp":"2025-06-11T08:25:58.6796243Z","logger":"common","caller":"dkafka/cdc.go:283","message":"terminating","error":"transform to kafka message at block_num: 239215080, cursor: kj3nvbrOJx51pyFN8z9E_qWzLpc-DFhsXAnkLxFJhYH2pXbH35zzCGF0ak6CxKrxiEC4Tlj-3trPEi94o8BTtYLpkrk1uyBqRi5_wIy7qOXnK_f0PQoYJO83WurbNInbUT7Uawj6f7AH5N_nb_fbZUA2ZJV3LmC3jDtYpNZQdKcX6HdklTX8c5nUharH94JE-uUmROGmnCijATN6JBtZNcSGYaGXuDQnMCRr, , StreamedAbiCodec.newCodec fail to create codec for schema UpdrsrcsttngActionNotification, error: CreateSchema on subject: 'io.dkafka.data.aom.mining.actions.v2.UpdrsrcsttngActionNotification', schema:\n{\"type\":\"record\",\"name\":\"UpdrsrcsttngActionNotification\",\"namespace\":\"io.dkafka.data.aom.mining.actions.v2\",\"fields\":[{\"name\":\"context\",\"type\":{\"type\":\"record\",\"name\":\"NotificationContext\",\"namespace\":\"io.dkafka\",\"fields\":[{\"name\":\"block_num\",\"type\":\"long\"},{\"name\":\"block_id\",\"type\":\"string\"},{\"name\":\"status\",\"type\":\"string\"},{\"name\":\"executed\",\"type\":\"boolean\"},{\"name\":\"block_step\",\"type\":\"string\"},{\"name\":\"correlation\",\"type\":[\"null\",{\"type\":\"record\",\"name\":\"Correlation\",\"namespace\":\"io.dkafka\",\"fields\":[{\"name\":\"payer\",\"type\":\"string\"},{\"name\":\"id\",\"type\":\"string\"}]}],\"default\":null},{\"name\":\"trx_id\",\"type\":\"string\"},{\"name\":\"time\",\"type\":{\"eos.type\":\"block_timestamp_type\",\"logicalType\":\"timestamp-millis\",\"type\":\"long\"}},{\"name\":\"cursor\",\"type\":\"string\"}]}},{\"name\":\"act_info\",\"type\":{\"type\":\"record\",\"name\":\"UpdrsrcsttngActionInfo\",\"fields\":[{\"name\":\"account\",\"type\":\"string\"},{\"name\":\"receiver\",\"type\":\"string\"},{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"global_seq\",\"type\":\"long\"},{\"name\":\"authorizations\",\"type\":{\"type\":\"array\",\"items\":\"string\"}},{\"name\":\"action_ordinal\",\"type\":\"long\"},{\"name\":\"creator_action_ordinal\",\"type\":\"long\"},{\"name\":\"closest_unnotified_ancestor_action_ordinal\",\"type\":\"long\"},{\"name\":\"execution_index\",\"type\":\"long\"},{\"name\":\"json_data\",\"type\":{\"type\":\"record\",\"name\":\"UpdrsrcsttngActionParams\",\"fields\":[{\"name\":\"row\",\"type\":{\"type\":\"record\",\"name\":\"MiningResourceSettings\",\"fields\":[{\"name\":\"resource_symbol\",\"type\":{\"eos.type\":\"symbol\",\"type\":\"string\"}},{\"name\":\"resource_type\",\"type\":{\"eos.type\":\"string\",\"type\":\"string\"}},{\"name\":\"to_be_prospected\",\"type\":{\"eos.type\":\"uint64\",\"logicalType\":\"eos.uint64\",\"type\":\"long\"}},{\"name\":\"min_droprate_qty\",\"type\":{\"eos.type\":\"uint64\",\"logicalType\":\"eos.uint64\",\"type\":\"long\"}},{\"name\":\"max_droprate_qty\",\"type\":{\"eos.type\":\"uint64\",\"logicalType\":\"eos.uint64\",\"type\":\"long\"}},{\"name\":\"base_daily_mining_qty\",\"type\":{\"eos.type\":\"uint64\",\"logicalType\":\"eos.uint64\",\"type\":\"long\"}},{\"name\":\"base_storage_qty\",\"type\":{\"eos.type\":\"uint64\",\"logicalType\":\"eos.uint64\",\"type\":\"long\"}},{\"name\":\"max_storage_qty\",\"type\":{\"eos.type\":\"uint64\",\"logicalType\":\"eos.uint64\",\"type\":\"long\"}},{\"name\":\"production_speed_upgrade_increment\",\"type\":{\"eos.type\":\"float64\",\"type\":\"double\"}},{\"name\":\"max_production_speed\",\"type\":{\"eos.type\":\"float64\",\"type\":\"double\"}}]}}]}},{\"name\":\"db_ops\",\"type\":{\"type\":\"array\",\"items\":{\"type\":\"record\",\"name\":\"DBOpBasic\",\"namespace\":\"io.dkafka\",\"fields\":[{\"name\":\"operation\",\"type\":[\"null\",\"int\"],\"default\":null},{\"name\":\"action_index\",\"type\":[\"null\",\"long\"],\"default\":null},{\"name\":\"index\",\"type\":\"int\"},{\"name\":\"code\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"scope\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"table_name\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"primary_key\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"old_payer\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"new_payer\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"old_data\",\"type\":[\"null\",\"bytes\"],\"default\":null},{\"name\":\"new_data\",\"type\":[\"null\",\"bytes\"],\"default\":null}]}}}]}}],\"meta\":{\"compatibility\":\"FORWARD\",\"type\":\"notification\",\"version\":\"2.239214999.0\",\"source\":\"dkafka-data-aom-mining-actions-59f59c6749-n7jh9\",\"domain\":\"aom.mining\"}} error: {\"error_code\":409,\"message\":\"Schema being registered is incompatible with an earlier schema for subject \\\"io.dkafka.data.aom.mining.actions.v2.UpdrsrcsttngActionNotification\\\"; error code: 409\"}","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"/app/cmd/dkafka/cdc.go","line":"283","function":"main.executeCdC"},"serviceContext":{"service":"common"}} +Error: transform to kafka message at block_num: 239215080, cursor: kj3nvbrOJx51pyFN8z9E_qWzLpc-DFhsXAnkLxFJhYH2pXbH35zzCGF0ak6CxKrxiEC4Tlj-3trPEi94o8BTtYLpkrk1uyBqRi5_wIy7qOXnK_f0PQoYJO83WurbNInbUT7Uawj6f7AH5N_nb_fbZUA2ZJV3LmC3jDtYpNZQdKcX6HdklTX8c5nUharH94JE-uUmROGmnCijATN6JBtZNcSGYaGXuDQnMCRr, , StreamedAbiCodec.newCodec fail to create codec for schema UpdrsrcsttngActionNotification, error: CreateSchema on subject: 'io.dkafka.data.aom.mining.actions.v2.UpdrsrcsttngActionNotification', schema: +{"type":"record","name":"UpdrsrcsttngActionNotification","namespace":"io.dkafka.data.aom.mining.actions.v2","fields":[{"name":"context","type":{"type":"record","name":"NotificationContext","namespace":"io.dkafka","fields":[{"name":"block_num","type":"long"},{"name":"block_id","type":"string"},{"name":"status","type":"string"},{"name":"executed","type":"boolean"},{"name":"block_step","type":"string"},{"name":"correlation","type":["null",{"type":"record","name":"Correlation","namespace":"io.dkafka","fields":[{"name":"payer","type":"string"},{"name":"id","type":"string"}]}],"default":null},{"name":"trx_id","type":"string"},{"name":"time","type":{"eos.type":"block_timestamp_type","logicalType":"timestamp-millis","type":"long"}},{"name":"cursor","type":"string"}]}},{"name":"act_info","type":{"type":"record","name":"UpdrsrcsttngActionInfo","fields":[{"name":"account","type":"string"},{"name":"receiver","type":"string"},{"name":"name","type":"string"},{"name":"global_seq","type":"long"},{"name":"authorizations","type":{"type":"array","items":"string"}},{"name":"action_ordinal","type":"long"},{"name":"creator_action_ordinal","type":"long"},{"name":"closest_unnotified_ancestor_action_ordinal","type":"long"},{"name":"execution_index","type":"long"},{"name":"json_data","type":{"type":"record","name":"UpdrsrcsttngActionParams","fields":[{"name":"row","type":{"type":"record","name":"MiningResourceSettings","fields":[{"name":"resource_symbol","type":{"eos.type":"symbol","type":"string"}},{"name":"resource_type","type":{"eos.type":"string","type":"string"}},{"name":"to_be_prospected","type":{"eos.type":"uint64","logicalType":"eos.uint64","type":"long"}},{"name":"min_droprate_qty","type":{"eos.type":"uint64","logicalType":"eos.uint64","type":"long"}},{"name":"max_droprate_qty","type":{"eos.type":"uint64","logicalType":"eos.uint64","type":"long"}},{"name":"base_daily_mining_qty","type":{"eos.type":"uint64","logicalType":"eos.uint64","type":"long"}},{"name":"base_storage_qty","type":{"eos.type":"uint64","logicalType":"eos.uint64","type":"long"}},{"name":"max_storage_qty","type":{"eos.type":"uint64","logicalType":"eos.uint64","type":"long"}},{"name":"production_speed_upgrade_increment","type":{"eos.type":"float64","type":"double"}},{"name":"max_production_speed","type":{"eos.type":"float64","type":"double"}}]}}]}},{"name":"db_ops","type":{"type":"array","items":{"type":"record","name":"DBOpBasic","namespace":"io.dkafka","fields":[{"name":"operation","type":["null","int"],"default":null},{"name":"action_index","type":["null","long"],"default":null},{"name":"index","type":"int"},{"name":"code","type":["null","string"],"default":null},{"name":"scope","type":["null","string"],"default":null},{"name":"table_name","type":["null","string"],"default":null},{"name":"primary_key","type":["null","string"],"default":null},{"name":"old_payer","type":["null","string"],"default":null},{"name":"new_payer","type":["null","string"],"default":null},{"name":"old_data","type":["null","bytes"],"default":null},{"name":"new_data","type":["null","bytes"],"default":null}]}}}]}}],"meta":{"compatibility":"FORWARD","type":"notification","version":"2.239214999.0","source":"dkafka-data-aom-mining-actions-59f59c6749-n7jh9","domain":"aom.mining"}} error: {"error_code":409,"message":"Schema being registered is incompatible with an earlier schema for subject \"io.dkafka.data.aom.mining.actions.v2.UpdrsrcsttngActionNotification\"; error code: 409"} diff --git a/fork/goavro/AUTHORS b/fork/goavro/AUTHORS new file mode 100644 index 0000000..3889c34 --- /dev/null +++ b/fork/goavro/AUTHORS @@ -0,0 +1,38 @@ +Goavro was originally created during the Fall of 2014 at LinkedIn, +Corp., in New York City, New York, USA. + +The following persons, listed in alphabetical order, have participated +with goavro development by contributing code and test cases. + + Alan Gardner + Billy Hand + Christian Blades + Corey Scott + Darshan Shaligram + Dylan Wen + Enrico Candino + Fellyn Silliman + James Crasta + Jeff Haynie + Joe Roth + Karrick S. McDermott + Kasey Klipsch + Michael Johnson + Murray Resinski + Nicolas Kaiser + Sebastien Launay + Thomas Desrosiers + kklipsch + seborama + +A big thank you to these persons who provided testing and amazing +feedback to goavro during its initial implementation: + + Dennis Ordanov + Thomas Desrosiers + +Also a big thank you is extended to our supervisors who supported our +efforts to bring goavro to the open source community: + + Greg Leffler + Nick Berry diff --git a/fork/goavro/LICENSE b/fork/goavro/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/fork/goavro/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/fork/goavro/README.md b/fork/goavro/README.md new file mode 100644 index 0000000..c65f3d7 --- /dev/null +++ b/fork/goavro/README.md @@ -0,0 +1,410 @@ +# goavro + +Goavro is a library that encodes and decodes Avro data. + +## Description + +* Encodes to and decodes from both binary and textual JSON Avro data. +* `Codec` is stateless and is safe to use by multiple goroutines. + +With the exception of features not yet supported, goavro attempts to +be fully compliant with the most recent version of the +[Avro specification](http://avro.apache.org/docs/1.8.2/spec.html). + +## Dependency Notice + +All usage of `gopkg.in` has been removed in favor of Go modules. +Please update your import paths to `github.com/linkedin/goavro/v2`. v1 +users can still use old versions of goavro by adding a constraint to +your `go.mod` or `Gopkg.toml` file. + +``` +require ( + github.com/linkedin/goavro v1.0.5 +) +``` + +```toml +[[constraint]] +name = "github.com/linkedin/goavro" +version = "=1.0.5" +``` + +## Major Improvements in v2 over v1 + +### Avro namespaces + +The original version of this library was written prior to my really +understanding how Avro namespaces ought to work. After using Avro for +a long time now, and after a lot of research, I think I grok Avro +namespaces properly, and the library now correctly handles every test +case the Apache Avro distribution has for namespaces, including being +able to refer to a previously defined data type later on in the same +schema. + +### Getting Data into and out of Records + +The original version of this library required creating `goavro.Record` +instances, and use of getters and setters to access a record's +fields. When schemas were complex, this required a lot of work to +debug and get right. The original version also required users to break +schemas in chunks, and have a different schema for each record +type. This was cumbersome, annoying, and error prone. + +The new version of this library eliminates the `goavro.Record` type, +and accepts a native Go map for all records to be encoded. Keys are +the field names, and values are the field values. Nothing could be +more easy. Conversely, decoding Avro data yields a native Go map for +the upstream client to pull data back out of. + +Furthermore, there is never a reason to ever have to break your schema +down into record schemas. Merely feed the entire schema into the +`NewCodec` function once when you create the `Codec`, then use +it. This library knows how to parse the data provided to it and ensure +data values for records and their fields are properly encoded and +decoded. + +### 3x--4x Performance Improvement + +The original version of this library was truly written with Go's idea +of `io.Reader` and `io.Writer` composition in mind. Although +composition is a powerful tool, the original library had to pull bytes +off the `io.Reader`--often one byte at a time--check for read errors, +decode the bytes, and repeat. This version, by using a native Go byte +slice, both decoding and encoding complex Avro data here at LinkedIn +is between three and four times faster than before. + +### Avro JSON Support + +The original version of this library did not support JSON encoding or +decoding, because it wasn't deemed useful for our internal use at the +time. When writing the new version of the library I decided to tackle +this issue once and for all, because so many engineers needed this +functionality for their work. + +### Better Handling of Record Field Default Values + +The original version of this library did not well handle default +values for record fields. This version of the library uses a default +value of a record field when encoding from native Go data to Avro data +and the record field is not specified. Additionally, when decoding +from Avro JSON data to native Go data, and a field is not specified, +the default value will be used to populate the field. + +## Contrast With Code Generation Tools + +If you have the ability to rebuild and redeploy your software whenever +data schemas change, code generation tools might be the best solution +for your application. + +There are numerous excellent tools for generating source code to +translate data between native and Avro binary or textual data. One +such tool is linked below. If a particular application is designed to +work with a rarely changing schema, programs that use code generated +functions can potentially be more performant than a program that uses +goavro to create a `Codec` dynamically at run time. + +* [gogen-avro](https://github.com/alanctgardner/gogen-avro) + +I recommend benchmarking the resultant programs using typical data +using both the code generated functions and using goavro to see which +performs better. Not all code generated functions will out perform +goavro for all data corpuses. + +If you don't have the ability to rebuild and redeploy software updates +whenever a data schema change occurs, goavro could be a great fit for +your needs. With goavro, your program can be given a new schema while +running, compile it into a `Codec` on the fly, and immediately start +encoding or decoding data using that `Codec`. Because Avro encoding +specifies that encoded data always be accompanied by a schema this is +not usually a problem. If the schema change is backwards compatible, +and the portion of your program that handles the decoded data is still +able to reference the decoded fields, there is nothing that needs to +be done when the schema change is detected by your program when using +goavro `Codec` instances to encode or decode data. + +## Resources + +* [Avro CLI Examples](https://github.com/miguno/avro-cli-examples) +* [Avro](https://avro.apache.org/) +* [Google Snappy](https://google.github.io/snappy/) +* [JavaScript Object Notation, JSON](https://www.json.org/) +* [Kafka](https://kafka.apache.org) + +## Usage + +Documentation is available via +[![GoDoc](https://godoc.org/github.com/linkedin/goavro?status.svg)](https://godoc.org/github.com/linkedin/goavro). + +```Go +package main + +import ( + "fmt" + + "github.com/linkedin/goavro/v2" +) + +func main() { + codec, err := goavro.NewCodec(` + { + "type": "record", + "name": "LongList", + "fields" : [ + {"name": "next", "type": ["null", "LongList"], "default": null} + ] + }`) + if err != nil { + fmt.Println(err) + } + + // NOTE: May omit fields when using default value + textual := []byte(`{"next":{"LongList":{}}}`) + + // Convert textual Avro data (in Avro JSON format) to native Go form + native, _, err := codec.NativeFromTextual(textual) + if err != nil { + fmt.Println(err) + } + + // Convert native Go form to binary Avro data + binary, err := codec.BinaryFromNative(nil, native) + if err != nil { + fmt.Println(err) + } + + // Convert binary Avro data back to native Go form + native, _, err = codec.NativeFromBinary(binary) + if err != nil { + fmt.Println(err) + } + + // Convert native Go form to textual Avro data + textual, err = codec.TextualFromNative(nil, native) + if err != nil { + fmt.Println(err) + } + + // NOTE: Textual encoding will show all fields, even those with values that + // match their default values + fmt.Println(string(textual)) + // Output: {"next":{"LongList":{"next":null}}} +} +``` + +Also please see the example programs in the `examples` directory for +reference. + +### ab2t + +The `ab2t` program is similar to the reference standard +`avrocat` program and converts Avro OCF files to Avro JSON +encoding. + +### arw + +The Avro-ReWrite program, `arw`, can be used to rewrite an +Avro OCF file while optionally changing the block counts, the +compression algorithm. `arw` can also upgrade the schema provided the +existing datum values can be encoded with the newly provided schema. + +### avroheader + +The Avro Header program, `avroheader`, can be used to print various +header information from an OCF file. + +### splice + +The `splice` program can be used to splice together an OCF file from +an Avro schema file and a raw Avro binary data file. + +### Translating Data + +A `Codec` provides four methods for translating between a byte slice +of either binary or textual Avro data and native Go data. + +The following methods convert data between native Go data and byte +slices of the binary Avro representation: + + BinaryFromNative + NativeFromBinary + +The following methods convert data between native Go data and byte +slices of the textual Avro representation: + + NativeFromTextual + TextualFromNative + +Each `Codec` also exposes the `Schema` method to return a simplified +version of the JSON schema string used to create the `Codec`. + +#### Translating From Avro to Go Data + +Goavro does not use Go's structure tags to translate data between +native Go types and Avro encoded data. + +When translating from either binary or textual Avro to native Go data, +goavro returns primitive Go data values for corresponding Avro data +values. The table below shows how goavro translates Avro types to Go +types. + +| Avro | Go     | +| ------------------ | ------------------------ | +| `null` | `nil` | +| `boolean` | `bool` | +| `bytes` | `[]byte` | +| `float` | `float32` | +| `double` | `float64` | +| `long` | `int64` | +| `int` | `int32`   | +| `string` | `string` | +| `array` | `[]interface{}` | +| `enum` | `string` | +| `fixed` | `[]byte`       | +| `map` and `record` | `map[string]interface{}` | +| `union` | *see below*    | + +Because of encoding rules for Avro unions, when an union's value is +`null`, a simple Go `nil` is returned. However when an union's value +is non-`nil`, a Go `map[string]interface{}` with a single key is +returned for the union. The map's single key is the Avro type name and +its value is the datum's value. + +#### Translating From Go to Avro Data + +Goavro does not use Go's structure tags to translate data between +native Go types and Avro encoded data. + +When translating from native Go to either binary or textual Avro data, +goavro generally requires the same native Go data types as the decoder +would provide, with some exceptions for programmer convenience. Goavro +will accept any numerical data type provided there is no precision +lost when encoding the value. For instance, providing `float64(3.0)` +to an encoder expecting an Avro `int` would succeed, while sending +`float64(3.5)` to the same encoder would return an error. + +When providing a slice of items for an encoder, the encoder will +accept either `[]interface{}`, or any slice of the required type. For +instance, when the Avro schema specifies: +`{"type":"array","items":"string"}`, the encoder will accept either +`[]interface{}`, or `[]string`. If given `[]int`, the encoder will +return an error when it attempts to encode the first non-string array +value using the string encoder. + +When providing a value for an Avro union, the encoder will accept +`nil` for a `null` value. If the value is non-`nil`, it must be a +`map[string]interface{}` with a single key-value pair, where the key +is the Avro type name and the value is the datum's value. As a +convenience, the `Union` function wraps any datum value in a map as +specified above. + +```Go +func ExampleUnion() { + codec, err := goavro.NewCodec(`["null","string","int"]`) + if err != nil { + fmt.Println(err) + } + buf, err := codec.TextualFromNative(nil, goavro.Union("string", "some string")) + if err != nil { + fmt.Println(err) + } + fmt.Println(string(buf)) + // Output: {"string":"some string"} +} +``` + +## Limitations + +Goavro is a fully featured encoder and decoder of binary and textual +JSON Avro data. It fully supports recursive data structures, unions, +and namespacing. It does have a few limitations that have yet to be +implemented. + +### Aliases + +The Avro specification allows an implementation to optionally map a +writer's schema to a reader's schema using aliases. Although goavro +can compile schemas with aliases, it does not yet implement this +feature. + +### Kafka Streams + +[Kafka](http://kafka.apache.org) is the reason goavro was +written. Similar to Avro Object Container Files being a layer of +abstraction above Avro Data Serialization format, Kafka's use of Avro +is a layer of abstraction that also sits above Avro Data Serialization +format, but has its own schema. Like Avro Object Container Files, this +has been implemented but removed until the API can be improved. + +### Default Maximum Block Counts, and Block Sizes + +When decoding arrays, maps, and OCF files, the Avro specification +states that the binary includes block counts and block sizes that +specify how many items are in the next block, and how many bytes are +in the next block. To prevent possible denial-of-service attacks on +clients that use this library caused by attempting to decode +maliciously crafted data, decoded block counts and sizes are compared +against public library variables MaxBlockCount and MaxBlockSize. When +the decoded values exceed these values, the decoder returns an error. + +Because not every upstream client is the same, we've chosen some sane +defaults for these values, but left them as mutable variables, so that +clients are able to override if deemed necessary for their +purposes. Their initial default values are (`math.MaxInt32` or +~2.2GB). + +### Schema Evolution + +Please see [my reasons why schema evolution is broken for Avro +1.x](https://github.com/linkedin/goavro/blob/master/SCHEMA-EVOLUTION.md). + +## License + +### Goavro license + +Copyright 2017 LinkedIn Corp. 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. + +### Google Snappy license + +Copyright (c) 2011 The Snappy-Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## Third Party Dependencies + +### Google Snappy + +Goavro links with [Google Snappy](http://google.github.io/snappy/) +to provide Snappy compression and decompression support. diff --git a/fork/goavro/SCHEMA-EVOLUTION.md b/fork/goavro/SCHEMA-EVOLUTION.md new file mode 100644 index 0000000..f8d75a4 --- /dev/null +++ b/fork/goavro/SCHEMA-EVOLUTION.md @@ -0,0 +1,92 @@ +From the Avro specification: + + default: A default value for this field, used when reading instances + that lack this field (optional). Permitted values depend on the + field's schema type, according to the table below. Default values for + union fields correspond to the first schema in the union. Default + values for bytes and fixed fields are JSON strings, where Unicode code + points 0-255 are mapped to unsigned 8-bit byte values 0-255. I read + the above to mean that the purpose of default values are to allow + reading Avro data that was written without the fields, and not + necessarily augmentation of data being serialized. So in general I + agree with you in terms of purpose. + +One very important aspect of Avro is that the schema used to serialize +the data should always remain with the data, so that a reader would +always be able to read the schema and then be able to consume the +data. I think most people still agree so far. + +However, this is where things get messy. Schema evolution is +frequently cited when folks want to use a new version of the schema to +read data that was once written using an older version of that schema. +I do not believe the Avro specification properly handles schema +evolution. Here's a simple example: + +``` +Record v0: + name: string + nickname: string, default: "" +``` + +``` +Record v1: + name: string + nickname: string, default: "" + title: string, default: "" +``` + +Okay, now a binary stream of records is just a bunch of strings. Let's +do that now. + +``` +0x0A, A, l, i, c, e, 0x06, B, o, b, 0x0A, B, r, u, c, e, 0x0A, S, a, l, l, y, 0x06, A, n, n +``` + +How many records is that? It could be as many as 5 records, each of a +single name and no nicknames. It could be as few as 2 records, one of +them with a nickname and a title, and one with only a nickname, or a +title. + +Now to drive home the nail that Avro schema evolution is broken, even +if each record had a header that indicated how many bytes it would +consume, we could know where one record began and ended, and how many +records there are. But if we were to read a record with two strings +in it, is the second string the nickname or the title? + +The Avro specification has no answer to that question, so neither do I. + +Effectively, Avro could be a great tool for serializing complex data, +but it's broken in its current form, and to fix it would require it to +break compatibility with itself, effectively rendering any binary data +serialized in a previous version of Avro unreadable by new versions, +unless it had some sort of version marker on the data so a library +could branch. + +One great solution would be augmenting the binary encoding with a +simple field number identifier. Let's imagine an Avro 2.x that had +this feature, and would support schema evolution. Here's an example +stream of bytes that could be unambiguously decoded using the new +schema: + +``` +0x02, 0x0A, A, l, i, c, e, 0x02, 0x06, B, o, B, 0x04, 0x0A, B, r, u, c, e, 0x02, 0x0C, C, h, a, r, l, i, e, 0x06, 0x04, M, r +``` + +In the above example of my fake Avro 2.0, this can be +deterministically decoded because 0x02 indicates the following is +field number 1 (name), followed by string length 5, followed by +Alice. + +Then the decoder would see 0x02, marking field number 1 again, +which means, "next record", followed by string length 3, followed by +Bob, followed by 0x04, which means field number 2 (nickname), followed +by string length 5, followed by Bruce. + +Followed by field number 1 (next record), followed by string length 6, +followed by Charlie, followed by field number 3 (title), followed by +string length 2, followed by Mr. + +In my hypothetical version of Avro 2, Avro can cope with schema +evolution using record defaults and such. Sadly, Avro 1.x cannot and +thus we should avoid using it if your use-case requires schema +evolution. diff --git a/fork/goavro/array.go b/fork/goavro/array.go new file mode 100644 index 0000000..c9e90aa --- /dev/null +++ b/fork/goavro/array.go @@ -0,0 +1,226 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "fmt" + "io" + "math" + "reflect" +) + +func makeArrayCodec(converters map[string]ConvertBuild, st map[string]*Codec, enclosingNamespace string, schemaMap map[string]interface{}, cb *codecBuilder) (*Codec, error) { + // array type must have items + itemSchema, ok := schemaMap["items"] + if !ok { + return nil, fmt.Errorf("Array ought to have items key") + } + itemCodec, err := buildCodec(converters, st, enclosingNamespace, itemSchema, cb) + if err != nil { + return nil, fmt.Errorf("Array items ought to be valid Avro type: %s", err) + } + + return &Codec{ + typeName: &name{"array", nullNamespace}, + nativeFromBinary: func(buf []byte) (interface{}, []byte, error) { + var value interface{} + var err error + + // block count and block size + if value, buf, err = longNativeFromBinary(buf); err != nil { + return nil, nil, fmt.Errorf("cannot decode binary array block count: %s", err) + } + blockCount := value.(int64) + if blockCount < 0 { + // NOTE: A negative block count implies there is a long encoded + // block size following the negative block count. We have no use + // for the block size in this decoder, so we read and discard + // the value. + if blockCount == math.MinInt64 { + // The minimum number for any signed numerical type can never be made positive + return nil, nil, fmt.Errorf("cannot decode binary array with block count: %d", blockCount) + } + blockCount = -blockCount // convert to its positive equivalent + if _, buf, err = longNativeFromBinary(buf); err != nil { + return nil, nil, fmt.Errorf("cannot decode binary array block size: %s", err) + } + } + // Ensure block count does not exceed some sane value. + if blockCount > MaxBlockCount { + return nil, nil, fmt.Errorf("cannot decode binary array when block count exceeds MaxBlockCount: %d > %d", blockCount, MaxBlockCount) + } + // NOTE: While the attempt of a RAM optimization shown below is not + // necessary, many encoders will encode all items in a single block. + // We can optimize amount of RAM allocated by runtime for the array + // by initializing the array for that number of items. + arrayValues := make([]interface{}, 0, blockCount) + + for blockCount != 0 { + // Decode `blockCount` datum values from buffer + for i := int64(0); i < blockCount; i++ { + if value, buf, err = itemCodec.nativeFromBinary(buf); err != nil { + return nil, nil, fmt.Errorf("cannot decode binary array item %d: %s", i+1, err) + } + arrayValues = append(arrayValues, value) + } + // Decode next blockCount from buffer, because there may be more blocks + if value, buf, err = longNativeFromBinary(buf); err != nil { + return nil, nil, fmt.Errorf("cannot decode binary array block count: %s", err) + } + blockCount = value.(int64) + if blockCount < 0 { + // NOTE: A negative block count implies there is a long + // encoded block size following the negative block count. We + // have no use for the block size in this decoder, so we + // read and discard the value. + if blockCount == math.MinInt64 { + // The minimum number for any signed numerical type can + // never be made positive + return nil, nil, fmt.Errorf("cannot decode binary array with block count: %d", blockCount) + } + blockCount = -blockCount // convert to its positive equivalent + if _, buf, err = longNativeFromBinary(buf); err != nil { + return nil, nil, fmt.Errorf("cannot decode binary array block size: %s", err) + } + } + // Ensure block count does not exceed some sane value. + if blockCount > MaxBlockCount { + return nil, nil, fmt.Errorf("cannot decode binary array when block count exceeds MaxBlockCount: %d > %d", blockCount, MaxBlockCount) + } + } + return arrayValues, buf, nil + }, + binaryFromNative: func(buf []byte, datum interface{}) ([]byte, error) { + arrayValues, err := convertArray(datum) + if err != nil { + return nil, fmt.Errorf("cannot encode binary array: %s", err) + } + + arrayLength := int64(len(arrayValues)) + var alreadyEncoded, remainingInBlock int64 + + for i, item := range arrayValues { + if remainingInBlock == 0 { // start a new block + remainingInBlock = arrayLength - alreadyEncoded + if remainingInBlock > MaxBlockCount { + // limit block count to MacBlockCount + remainingInBlock = MaxBlockCount + } + buf, _ = longBinaryFromNative(buf, remainingInBlock) + } + + if buf, err = itemCodec.binaryFromNative(buf, item); err != nil { + return nil, fmt.Errorf("cannot encode binary array item %d: %v: %s", i+1, item, err) + } + + remainingInBlock-- + alreadyEncoded++ + } + + return longBinaryFromNative(buf, 0) // append trailing 0 block count to signal end of Array + }, + nativeFromTextual: func(buf []byte) (interface{}, []byte, error) { + var arrayValues []interface{} + var value interface{} + var err error + var b byte + + if buf, err = advanceAndConsume(buf, '['); err != nil { + return nil, nil, fmt.Errorf("cannot decode textual array: %s", err) + } + if buf, _ = advanceToNonWhitespace(buf); len(buf) == 0 { + return nil, nil, fmt.Errorf("cannot decode textual array: %s", io.ErrShortBuffer) + } + // NOTE: Special case for empty array + if buf[0] == ']' { + return arrayValues, buf[1:], nil + } + + // NOTE: Also terminates when read ']' byte. + for len(buf) > 0 { + // decode value + value, buf, err = itemCodec.nativeFromTextual(buf) + if err != nil { + return nil, nil, fmt.Errorf("cannot decode textual array: %s", err) + } + arrayValues = append(arrayValues, value) + // either comma or closing curly brace + if buf, _ = advanceToNonWhitespace(buf); len(buf) == 0 { + return nil, nil, fmt.Errorf("cannot decode textual array: %s", io.ErrShortBuffer) + } + switch b = buf[0]; b { + case ']': + return arrayValues, buf[1:], nil + case ',': + // no-op + default: + return nil, nil, fmt.Errorf("cannot decode textual array: expected ',' or ']'; received: %q", b) + } + // NOTE: consume comma from above + if buf, _ = advanceToNonWhitespace(buf[1:]); len(buf) == 0 { + return nil, nil, fmt.Errorf("cannot decode textual array: %s", io.ErrShortBuffer) + } + } + return nil, buf, io.ErrShortBuffer + }, + textualFromNative: func(buf []byte, datum interface{}) ([]byte, error) { + arrayValues, err := convertArray(datum) + if err != nil { + return nil, fmt.Errorf("cannot encode textual array: %s", err) + } + + var atLeastOne bool + + buf = append(buf, '[') + + for i, item := range arrayValues { + atLeastOne = true + + // Encode value + buf, err = itemCodec.textualFromNative(buf, item) + if err != nil { + // field was specified in datum; therefore its value was invalid + return nil, fmt.Errorf("cannot encode textual array item %d; %v: %s", i+1, item, err) + } + buf = append(buf, ',') + } + + if atLeastOne { + return append(buf[:len(buf)-1], ']'), nil + } + return append(buf, ']'), nil + }, + }, nil +} + +// convertArray converts interface{} to []interface{} if possible. +func convertArray(datum interface{}) ([]interface{}, error) { + arrayValues, ok := datum.([]interface{}) + if ok { + return arrayValues, nil + } + // NOTE: When given a slice of any other type, zip values to + // items as a convenience to client. + v := reflect.ValueOf(datum) + if v.Kind() != reflect.Slice { + return nil, fmt.Errorf("cannot create []interface{}: expected slice; received: %T", datum) + } + // NOTE: Two better alternatives to the current algorithm are: + // (1) mutate the reflection tuple underneath to convert the + // []int, for example, to []interface{}, with O(1) complexity + // (2) use copy builtin to zip the data items over with O(n) complexity, + // but more efficient than what's below. + // Suggestions? + arrayValues = make([]interface{}, v.Len()) + for idx := 0; idx < v.Len(); idx++ { + arrayValues[idx] = v.Index(idx).Interface() + } + return arrayValues, nil +} diff --git a/fork/goavro/array_test.go b/fork/goavro/array_test.go new file mode 100644 index 0000000..1768789 --- /dev/null +++ b/fork/goavro/array_test.go @@ -0,0 +1,138 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "testing" +) + +func TestArraySchema(t *testing.T) { + testSchemaValid(t, `{"type":"array","items":"bytes"}`) + testSchemaInvalid(t, `{"type":"array","item":"int"}`, "Array ought to have items key") + testSchemaInvalid(t, `{"type":"array","items":"integer"}`, "Array items ought to be valid Avro type") + testSchemaInvalid(t, `{"type":"array","items":3}`, "Array items ought to be valid Avro type") + testSchemaInvalid(t, `{"type":"array","items":int}`, "invalid character") // type name must be quoted +} + +func TestArrayDecodeInitialBlockCountCannotDecode(t *testing.T) { + testBinaryDecodeFail(t, `{"type":"array","items":"int"}`, nil, "block count") +} + +func TestArrayDecodeInitialBlockCountZero(t *testing.T) { + testBinaryDecodePass(t, `{"type":"array","items":"int"}`, []interface{}{}, []byte{0}) +} + +func TestArrayDecodeInitialBlockCountNegative(t *testing.T) { + testBinaryDecodePass(t, `{"type":"array","items":"int"}`, []interface{}{3}, []byte{1, 2, 6, 0}) +} + +func TestArrayDecodeInitialBlockCountTooLarge(t *testing.T) { + testBinaryDecodeFail(t, `{"type":"array","items":"int"}`, morePositiveThanMaxBlockCount, "block count") +} + +func TestArrayDecodeInitialBlockCountNegativeTooLarge(t *testing.T) { + testBinaryDecodeFail(t, `{"type":"array","items":"int"}`, append(moreNegativeThanMaxBlockCount, byte(0)), "block count") +} + +func TestArrayDecodeInitialBlockCountTooNegative(t *testing.T) { + // -(uint8(-128)) == -128 + testBinaryDecodeFail(t, `{"type":"array","items":"int"}`, append(mostNegativeBlockCount, byte(0)), "block count") +} + +func TestArrayDecodeNextBlockCountCannotDecode(t *testing.T) { + testBinaryDecodeFail(t, `{"type":"array","items":"int"}`, []byte{2, 6}, "block count") +} + +func TestArrayDecodeNextBlockCountNegative(t *testing.T) { + testBinaryDecodePass(t, `{"type":"array","items":"int"}`, []interface{}{3, 3}, []byte{2, 6, 1, 2, 6, 0}) +} + +func TestArrayDecodeNextBlockCountTooLarge(t *testing.T) { + testBinaryDecodeFail(t, `{"type":"array","items":"int"}`, append([]byte{2, 6}, morePositiveThanMaxBlockCount...), "block count") +} + +func TestArrayDecodeNextBlockCountNegativeTooLarge(t *testing.T) { + testBinaryDecodeFail(t, `{"type":"array","items":"int"}`, append([]byte{2, 6}, append(moreNegativeThanMaxBlockCount, []byte{2, 6, 0}...)...), "block count") +} + +func TestArrayDecodeNextBlockCountTooNegative(t *testing.T) { + testBinaryDecodeFail(t, `{"type":"array","items":"int"}`, append([]byte{2, 6}, append(mostNegativeBlockCount, []byte{2, 6, 0}...)...), "block count") +} + +func TestArrayNull(t *testing.T) { + testBinaryCodecPass(t, `{"type":"array","items":"null"}`, []interface{}{}, []byte{0}) + testBinaryCodecPass(t, `{"type":"array","items":"null"}`, []interface{}{nil}, []byte{2, 0}) + testBinaryCodecPass(t, `{"type":"array","items":"null"}`, []interface{}{nil, nil}, []byte{4, 0}) +} + +func TestArrayReceiveSliceEmptyInterface(t *testing.T) { + testBinaryCodecPass(t, `{"type":"array","items":"boolean"}`, []interface{}{}, []byte{0}) + testBinaryCodecPass(t, `{"type":"array","items":"boolean"}`, []interface{}{false}, []byte{2, 0, 0}) + testBinaryCodecPass(t, `{"type":"array","items":"boolean"}`, []interface{}{true}, []byte{2, 1, 0}) + testBinaryCodecPass(t, `{"type":"array","items":"boolean"}`, []interface{}{false, false}, []byte{4, 0, 0, 0}) + testBinaryCodecPass(t, `{"type":"array","items":"boolean"}`, []interface{}{true, true}, []byte{4, 1, 1, 0}) +} + +func TestArrayBinaryReceiveSliceInt(t *testing.T) { + testBinaryCodecPass(t, `{"type":"array","items":"int"}`, []int{}, []byte{0}) + testBinaryCodecPass(t, `{"type":"array","items":"int"}`, []int{1}, []byte("\x02\x02\x00")) + testBinaryCodecPass(t, `{"type":"array","items":"int"}`, []int{1, 2}, []byte("\x04\x02\x04\x00")) +} + +func TestArrayTextualReceiveSliceInt(t *testing.T) { + testTextCodecPass(t, `{"type":"array","items":"int"}`, []int{}, []byte(`[]`)) + testTextCodecPass(t, `{"type":"array","items":"int"}`, []int{1}, []byte(`[1]`)) + testTextCodecPass(t, `{"type":"array","items":"int"}`, []int{1, 2}, []byte(`[1,2]`)) +} + +func TestArrayBytes(t *testing.T) { + testBinaryCodecPass(t, `{"type":"array","items":"bytes"}`, []interface{}(nil), []byte{0}) // item count == 0 + testBinaryCodecPass(t, `{"type":"array","items":"bytes"}`, []interface{}{[]byte("foo")}, []byte("\x02\x06foo\x00")) // item count == 1, item 1 size == 3, foo, no more items + testBinaryCodecPass(t, `{"type":"array","items":"bytes"}`, []interface{}{[]byte("foo"), []byte("bar")}, []byte("\x04\x06foo\x06bar\x00")) + + testBinaryCodecPass(t, `{"type":"array","items":"bytes"}`, [][]byte(nil), []byte{0}) // item count == 0 + testBinaryCodecPass(t, `{"type":"array","items":"bytes"}`, [][]byte{[]byte("foo")}, []byte("\x02\x06foo\x00")) // item count == 1, item 1 size == 3, foo, no more items + testBinaryCodecPass(t, `{"type":"array","items":"bytes"}`, [][]byte{[]byte("foo"), []byte("bar")}, []byte("\x04\x06foo\x06bar\x00")) +} + +func TestArrayEncodeError(t *testing.T) { + // provided slice of primitive types that are not compatible with schema + testBinaryEncodeFailBadDatumType(t, `{"type":"array","items":"int"}`, []string{"1"}) + testBinaryEncodeFailBadDatumType(t, `{"type":"array","items":"int"}`, []string{"1", "2"}) +} + +func TestArrayEncodeErrorFIXME(t *testing.T) { + // NOTE: Would be better if returns error, however, because only the size is encoded, the + // items encoder is never invoked to detect it is the wrong slice type + if false { + testBinaryEncodeFailBadDatumType(t, `{"type":"array","items":"int"}`, []string{}) + } else { + testBinaryCodecPass(t, `{"type":"array","items":"int"}`, []string{}, []byte{0}) + } +} + +func TestArrayTextDecodeFail(t *testing.T) { + schema := `{"type":"array","items":"string"}` + testTextDecodeFail(t, schema, []byte(` "v1" , "v2" ] `), "expected: '['") + testTextDecodeFail(t, schema, []byte(` [ 13 , "v2" ] `), "expected initial \"") + testTextDecodeFail(t, schema, []byte(` [ "v1 , "v2" ] `), "expected ',' or ']'") + testTextDecodeFail(t, schema, []byte(` [ "v1" "v2" ] `), "expected ',' or ']'") + testTextDecodeFail(t, schema, []byte(` [ "v1" , 13 ] `), "expected initial \"") + testTextDecodeFail(t, schema, []byte(` [ "v1" , "v2 ] `), "expected final \"") + testTextDecodeFail(t, schema, []byte(` [ "v1" , "v2" `), "short buffer") +} + +func TestArrayTextCodecPass(t *testing.T) { + schema := `{"type":"array","items":"string"}` + datum := []interface{}{"⌘ ", "value2"} + testTextEncodePass(t, schema, datum, []byte(`["\u0001\u2318 ","value2"]`)) + testTextDecodePass(t, schema, datum, []byte(` [ "\u0001\u2318 " , "value2" ]`)) + testTextCodecPass(t, schema, []interface{}{}, []byte(`[]`)) // empty array +} diff --git a/fork/goavro/benchmark_test.go b/fork/goavro/benchmark_test.go new file mode 100644 index 0000000..cd7c02a --- /dev/null +++ b/fork/goavro/benchmark_test.go @@ -0,0 +1,87 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "io/ioutil" + "testing" +) + +func BenchmarkNewCodecUsingV2(b *testing.B) { + schema, err := ioutil.ReadFile("fixtures/quickstop.avsc") + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = newCodecUsingV2(b, string(schema)) + } +} + +func BenchmarkNativeFromAvroUsingV2(b *testing.B) { + avroBlob, err := ioutil.ReadFile("fixtures/quickstop-null.avro") + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = nativeFromAvroUsingV2(b, avroBlob) + } +} + +func BenchmarkBinaryFromNativeUsingV2(b *testing.B) { + avroBlob, err := ioutil.ReadFile("fixtures/quickstop-null.avro") + if err != nil { + b.Fatal(err) + } + nativeData, codec := nativeFromAvroUsingV2(b, avroBlob) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = binaryFromNativeUsingV2(b, codec, nativeData) + } +} + +func BenchmarkNativeFromBinaryUsingV2(b *testing.B) { + avroBlob, err := ioutil.ReadFile("fixtures/quickstop-null.avro") + if err != nil { + b.Fatal(err) + } + nativeData, codec := nativeFromAvroUsingV2(b, avroBlob) + binaryData := binaryFromNativeUsingV2(b, codec, nativeData) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = nativeFromBinaryUsingV2(b, codec, binaryData) + } +} + +func BenchmarkTextualFromNativeUsingV2(b *testing.B) { + avroBlob, err := ioutil.ReadFile("fixtures/quickstop-null.avro") + if err != nil { + b.Fatal(err) + } + nativeData, codec := nativeFromAvroUsingV2(b, avroBlob) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = textFromNativeUsingV2(b, codec, nativeData) + } +} + +func BenchmarkNativeFromTextualUsingV2(b *testing.B) { + avroBlob, err := ioutil.ReadFile("fixtures/quickstop-null.avro") + if err != nil { + b.Fatal(err) + } + nativeData, codec := nativeFromAvroUsingV2(b, avroBlob) + textData := textFromNativeUsingV2(b, codec, nativeData) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = nativeFromTextUsingV2(b, codec, textData) + } +} diff --git a/fork/goavro/binaryReader.go b/fork/goavro/binaryReader.go new file mode 100644 index 0000000..a199237 --- /dev/null +++ b/fork/goavro/binaryReader.go @@ -0,0 +1,160 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "fmt" + "io" + "math" +) + +// bytesBinaryReader reads bytes from io.Reader and returns byte slice of +// specified size or the error encountered while trying to read those bytes. +func bytesBinaryReader(ior io.Reader) ([]byte, error) { + size, err := longBinaryReader(ior) + if err != nil { + return nil, fmt.Errorf("cannot read bytes: cannot read size: %s", err) + } + if size < 0 { + return nil, fmt.Errorf("cannot read bytes: size is negative: %d", size) + } + if size > MaxBlockSize { + return nil, fmt.Errorf("cannot read bytes: size exceeds MaxBlockSize: %d > %d", size, MaxBlockSize) + } + buf := make([]byte, size) + _, err = io.ReadAtLeast(ior, buf, int(size)) + if err != nil { + return nil, fmt.Errorf("cannot read bytes: %s", err) + } + return buf, nil +} + +// longBinaryReader reads bytes from io.Reader until has complete long value, or +// read error. +func longBinaryReader(ior io.Reader) (int64, error) { + var value uint64 + var shift uint + var err error + var b byte + + // NOTE: While benchmarks show it's more performant to invoke ReadByte when + // available, testing whether a variable's data type implements a particular + // method is quite slow too. So perform the test once, and branch to the + // appropriate loop based on the results. + + if byteReader, ok := ior.(io.ByteReader); ok { + for { + if b, err = byteReader.ReadByte(); err != nil { + return 0, err // NOTE: must send back unaltered error to detect io.EOF + } + value |= uint64(b&intMask) << shift + if b&intFlag == 0 { + return (int64(value>>1) ^ -int64(value&1)), nil + } + shift += 7 + } + } + + // NOTE: ior does not also implement io.ByteReader, so we must allocate a + // byte slice with a single byte, and read each byte into the slice. + buf := make([]byte, 1) + for { + if _, err = ior.Read(buf); err != nil { + return 0, err // NOTE: must send back unaltered error to detect io.EOF + } + b = buf[0] + value |= uint64(b&intMask) << shift + if b&intFlag == 0 { + return (int64(value>>1) ^ -int64(value&1)), nil + } + shift += 7 + } +} + +// metadataBinaryReader reads bytes from io.Reader until has entire map value, +// or read error. +func metadataBinaryReader(ior io.Reader) (map[string][]byte, error) { + var err error + var value interface{} + + // block count and block size + if value, err = longBinaryReader(ior); err != nil { + return nil, fmt.Errorf("cannot read map block count: %s", err) + } + blockCount := value.(int64) + if blockCount < 0 { + if blockCount == math.MinInt64 { + // The minimum number for any signed numerical type can never be + // made positive + return nil, fmt.Errorf("cannot read map with block count: %d", blockCount) + } + // NOTE: A negative block count implies there is a long encoded block + // size following the negative block count. We have no use for the block + // size in this decoder, so we read and discard the value. + blockCount = -blockCount // convert to its positive equivalent + if _, err = longBinaryReader(ior); err != nil { + return nil, fmt.Errorf("cannot read map block size: %s", err) + } + } + // Ensure block count does not exceed some sane value. + if blockCount > MaxBlockCount { + return nil, fmt.Errorf("cannot read map when block count exceeds MaxBlockCount: %d > %d", blockCount, MaxBlockCount) + } + // NOTE: While the attempt of a RAM optimization shown below is not + // necessary, many encoders will encode all items in a single block. We can + // optimize amount of RAM allocated by runtime for the array by initializing + // the array for that number of items. + mapValues := make(map[string][]byte, blockCount) + + for blockCount != 0 { + // Decode `blockCount` datum values from buffer + for i := int64(0); i < blockCount; i++ { + // first decode the key string + keyBytes, err := bytesBinaryReader(ior) + if err != nil { + return nil, fmt.Errorf("cannot read map key: %s", err) + } + key := string(keyBytes) + if _, ok := mapValues[key]; ok { + return nil, fmt.Errorf("cannot read map: duplicate key: %q", key) + } + // metadata values are always bytes + buf, err := bytesBinaryReader(ior) + if err != nil { + return nil, fmt.Errorf("cannot read map value for key %q: %s", key, err) + } + mapValues[key] = buf + } + // Decode next blockCount from buffer, because there may be more blocks + if value, err = longBinaryReader(ior); err != nil { + return nil, fmt.Errorf("cannot read map block count: %s", err) + } + blockCount = value.(int64) + if blockCount < 0 { + if blockCount == math.MinInt64 { + // The minimum number for any signed numerical type can never be + // made positive + return nil, fmt.Errorf("cannot read map with block count: %d", blockCount) + } + // NOTE: A negative block count implies there is a long encoded + // block size following the negative block count. We have no use for + // the block size in this decoder, so we read and discard the value. + blockCount = -blockCount // convert to its positive equivalent + if _, err = longBinaryReader(ior); err != nil { + return nil, fmt.Errorf("cannot read map block size: %s", err) + } + } + // Ensure block count does not exceed some sane value. + if blockCount > MaxBlockCount { + return nil, fmt.Errorf("cannot read map when block count exceeds MaxBlockCount: %d > %d", blockCount, MaxBlockCount) + } + } + return mapValues, nil +} diff --git a/fork/goavro/binary_test.go b/fork/goavro/binary_test.go new file mode 100644 index 0000000..dcf1d45 --- /dev/null +++ b/fork/goavro/binary_test.go @@ -0,0 +1,163 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "bytes" + "fmt" + "math" + "testing" +) + +var morePositiveThanMaxBlockCount, morePositiveThanMaxBlockSize, moreNegativeThanMaxBlockCount, mostNegativeBlockCount []byte + +func init() { + c, err := NewCodec(`"long"`) + if err != nil { + panic(err) + } + + morePositiveThanMaxBlockCount, err = c.BinaryFromNative(nil, (MaxBlockCount + 1)) + if err != nil { + panic(err) + } + + morePositiveThanMaxBlockSize, err = c.BinaryFromNative(nil, (MaxBlockSize + 1)) + if err != nil { + panic(err) + } + + moreNegativeThanMaxBlockCount, err = c.BinaryFromNative(nil, -(MaxBlockCount + 1)) + if err != nil { + panic(err) + } + + mostNegativeBlockCount, err = c.BinaryFromNative(nil, int64(math.MinInt64)) + if err != nil { + panic(err) + } +} + +func testBinaryDecodeFail(t *testing.T, schema string, buf []byte, errorMessage string) { + t.Helper() + c, err := NewCodec(schema) + if err != nil { + t.Fatal(err) + } + value, newBuffer, err := c.NativeFromBinary(buf) + ensureError(t, err, errorMessage) + if value != nil { + t.Errorf("GOT: %v; WANT: %v", value, nil) + } + if !bytes.Equal(buf, newBuffer) { + t.Errorf("GOT: %v; WANT: %v", newBuffer, buf) + } +} + +func testBinaryEncodeFail(t *testing.T, schema string, datum interface{}, errorMessage string) { + t.Helper() + c, err := NewCodec(schema) + if err != nil { + t.Fatal(err) + } + buf, err := c.BinaryFromNative(nil, datum) + ensureError(t, err, errorMessage) + if buf != nil { + t.Errorf("GOT: %v; WANT: %v", buf, nil) + } +} + +func testBinaryEncodeFailBadDatumType(t *testing.T, schema string, datum interface{}) { + t.Helper() + testBinaryEncodeFail(t, schema, datum, "received: ") +} + +func testBinaryDecodeFailShortBuffer(t *testing.T, schema string, buf []byte) { + t.Helper() + testBinaryDecodeFail(t, schema, buf, "short buffer") +} + +func testBinaryDecodePass(t *testing.T, schema string, datum interface{}, encoded []byte) { + t.Helper() + codec, err := NewCodec(schema) + if err != nil { + t.Fatal(err) + } + + value, remaining, err := codec.NativeFromBinary(encoded) + if err != nil { + t.Fatalf("schema: %s; %s", schema, err) + } + + // remaining ought to be empty because there is nothing remaining to be + // decoded + if actual, expected := len(remaining), 0; actual != expected { + t.Errorf("schema: %s; Datum: %v; Actual: %#v; Expected: %#v", schema, datum, actual, expected) + } + + // for testing purposes, to prevent big switch statement, convert each to + // string and compare. + if actual, expected := fmt.Sprintf("%v", value), fmt.Sprintf("%v", datum); actual != expected { + t.Errorf("schema: %s; Datum: %v; Actual: %#v; Expected: %#v", schema, datum, actual, expected) + } +} + +func testBinaryEncodePass(t *testing.T, schema string, datum interface{}, expected []byte) { + t.Helper() + codec, err := NewCodec(schema) + if err != nil { + t.Fatalf("schema: %q %s", schema, err) + } + + actual, err := codec.BinaryFromNative(nil, datum) + if err != nil { + t.Fatalf("schema: %s; Datum: %v; %s", schema, datum, err) + } + if !bytes.Equal(actual, expected) { + t.Errorf("schema: %s; Datum: %v; Actual: %#v; Expected: %#v", schema, datum, actual, expected) + } +} + +// testBinaryCodecPass does a bi-directional codec check, by encoding datum to +// bytes, then decoding bytes back to datum. +func testBinaryCodecPass(t *testing.T, schema string, datum interface{}, buf []byte) { + t.Helper() + testBinaryDecodePass(t, schema, datum, buf) + testBinaryEncodePass(t, schema, datum, buf) +} + +func testBinaryWriteReadPass(t *testing.T, schema string, datum interface{}) { + t.Helper() + codec, err := NewCodec(schema) + if err != nil { + t.Fatalf("Schema: %q %s", schema, err) + } + + actualBinary, err := codec.BinaryFromNative(nil, datum) + if err != nil { + t.Fatalf("schema: %s; Datum: %v; %s", schema, datum, err) + } + + value, remaining, err := codec.NativeFromBinary(actualBinary) + if err != nil { + t.Fatalf("schema: %s; %s", schema, err) + } + + // remaining ought to be empty because there is nothing remaining to be + // decoded + if actual, expected := len(remaining), 0; actual != expected { + t.Errorf("schema: %s; Datum: %v; Actual: %#v; Expected: %#v", schema, datum, actual, expected) + } + // for testing purposes, to prevent big switch statement, convert each to + // string and compare. + if actual, expected := fmt.Sprintf("%v", value), fmt.Sprintf("%v", datum); actual != expected { + t.Errorf("schema: %s; Datum: %v; Actual: %#v; Expected: %#v", schema, datum, actual, expected) + } +} diff --git a/fork/goavro/boolean.go b/fork/goavro/boolean.go new file mode 100644 index 0000000..3bd3b09 --- /dev/null +++ b/fork/goavro/boolean.go @@ -0,0 +1,116 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "bytes" + "errors" + "fmt" + "io" + "strconv" +) + +func booleanNativeFromBinary(buf []byte) (interface{}, []byte, error) { + if len(buf) < 1 { + return nil, nil, io.ErrShortBuffer + } + var b byte + b, buf = buf[0], buf[1:] + switch b { + case byte(0): + return false, buf, nil + case byte(1): + return true, buf, nil + default: + return nil, nil, fmt.Errorf("cannot decode binary boolean: expected: Go byte(0) or byte(1); received: byte(%d)", b) + } +} + +func booleanBinaryFromNative(buf []byte, datum interface{}) ([]byte, error) { + var i int + switch v := datum.(type) { + case bool: + if v { + i = 1 + } + case int: + i = int(v) + case int8: + i = int(v) + case int16: + i = int(v) + case int32: + i = int(v) + case int64: + i = int(v) + case uint: + i = int(v) + case uint8: + i = int(v) + case uint16: + i = int(v) + case uint32: + i = int(v) + case uint64: + i = int(v) + case float32: + i = int(v) + case float64: + i = int(v) + case string: + res, err := strconv.ParseBool(v) + if err != nil { + return nil, fmt.Errorf("cannot encode string boolean %v: because off: %v", v, err) + } else { + if res { + i = 1 + } else { + i = 0 + } + } + default: + return nil, fmt.Errorf("cannot encode binary boolean: expected: Go bool; received: %T", datum) + } + if i < 0 { + return nil, fmt.Errorf("cannot encode binary boolean: provided Go numeric is not equal or superior to 0: %v", datum) + } + var b byte + if i > 0 { + b = 1 + } + return append(buf, b), nil +} + +func booleanNativeFromTextual(buf []byte) (interface{}, []byte, error) { + if len(buf) < 4 { + return nil, nil, fmt.Errorf("cannot decode textual boolean: %s", io.ErrShortBuffer) + } + if bytes.Equal(buf[:4], []byte("true")) { + return true, buf[4:], nil + } + if len(buf) < 5 { + return nil, nil, fmt.Errorf("cannot decode textual boolean: %s", io.ErrShortBuffer) + } + if bytes.Equal(buf[:5], []byte("false")) { + return false, buf[5:], nil + } + return nil, nil, errors.New("expected false or true") +} + +func booleanTextualFromNative(buf []byte, datum interface{}) ([]byte, error) { + value, ok := datum.(bool) + if !ok { + return nil, fmt.Errorf("boolean: expected: Go bool; received: %T", datum) + } + if value { + return append(buf, "true"...), nil + } + return append(buf, "false"...), nil +} diff --git a/fork/goavro/boolean_test.go b/fork/goavro/boolean_test.go new file mode 100644 index 0000000..cad3124 --- /dev/null +++ b/fork/goavro/boolean_test.go @@ -0,0 +1,47 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "bytes" + "encoding/binary" + "testing" +) + +func Float64ToByte(f float64) []byte { + var buf bytes.Buffer + binary.Write(&buf, binary.LittleEndian, f) + return buf.Bytes() +} + +func TestSchemaPrimitiveCodecBoolean(t *testing.T) { + testSchemaPrimativeCodec(t, `"boolean"`) +} + +func TestPrimitiveBooleanBinary(t *testing.T) { + testBinaryEncodePass(t, `"boolean"`, 0, []byte{0}) + testBinaryEncodePass(t, `"boolean"`, 1, []byte{1}) + testBinaryEncodePass(t, `"boolean"`, 2, []byte{1}) + testBinaryEncodeFail(t, `"boolean"`, -1, "cannot encode binary boolean: provided Go numeric is not equal or superior to 0: -1") + testBinaryEncodePass(t, `"boolean"`, 42.0, []byte{1}) + testBinaryDecodeFailShortBuffer(t, `"boolean"`, nil) + testBinaryCodecPass(t, `"boolean"`, false, []byte{0}) + testBinaryCodecPass(t, `"boolean"`, true, []byte{1}) + testBinaryEncodePass(t, `"boolean"`, float64(0), []byte{0}) + testBinaryEncodePass(t, `"boolean"`, float64(1), []byte{1}) +} + +func TestPrimitiveBooleanText(t *testing.T) { + testTextEncodeFailBadDatumType(t, `"boolean"`, 0) + testTextEncodeFailBadDatumType(t, `"boolean"`, 1) + testTextDecodeFailShortBuffer(t, `"boolean"`, nil) + testTextCodecPass(t, `"boolean"`, false, []byte("false")) + testTextCodecPass(t, `"boolean"`, true, []byte("true")) +} diff --git a/fork/goavro/bytes.go b/fork/goavro/bytes.go new file mode 100644 index 0000000..94b0fd2 --- /dev/null +++ b/fork/goavro/bytes.go @@ -0,0 +1,546 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "encoding/hex" + "errors" + "fmt" + "io" + "os" + "reflect" + "unicode" + "unicode/utf16" + "unicode/utf8" +) + +//////////////////////////////////////// +// Binary Decode +//////////////////////////////////////// + +func bytesNativeFromBinary(buf []byte) (interface{}, []byte, error) { + if len(buf) < 1 { + return nil, nil, fmt.Errorf("cannot decode binary bytes: %s", io.ErrShortBuffer) + } + var decoded interface{} + var err error + if decoded, buf, err = longNativeFromBinary(buf); err != nil { + return nil, nil, fmt.Errorf("cannot decode binary bytes: %s", err) + } + size := decoded.(int64) // always returns int64 + if size < 0 { + return nil, nil, fmt.Errorf("cannot decode binary bytes: negative size: %d", size) + } + if size > int64(len(buf)) { + return nil, nil, fmt.Errorf("cannot decode binary bytes: %s", io.ErrShortBuffer) + } + return buf[:size], buf[size:], nil +} + +func stringNativeFromBinary(buf []byte) (interface{}, []byte, error) { + d, b, err := bytesNativeFromBinary(buf) + if err != nil { + return nil, nil, fmt.Errorf("cannot decode binary string: %s", err) + } + return string(d.([]byte)), b, nil +} + +//////////////////////////////////////// +// Binary Encode +//////////////////////////////////////// + +func bytesBinaryFromNative(buf []byte, datum interface{}) ([]byte, error) { + var someBytes []byte + switch d := datum.(type) { + case []byte: + someBytes = d + case string: + someBytes = []byte(d) + default: + valueOf := reflect.ValueOf(d) + switch kind := valueOf.Kind(); { + case kind == reflect.Slice && valueOf.Type().Elem().Kind() == reflect.Uint8: + someBytes = valueOf.Bytes() + case kind == reflect.String: + someBytes = []byte(valueOf.String()) + default: + return nil, fmt.Errorf("cannot encode binary bytes: expected: []byte or string; received: %T", datum) + } + } + buf, _ = longBinaryFromNative(buf, len(someBytes)) // only fails when given non integer + return append(buf, someBytes...), nil // append datum bytes +} + +func stringBinaryFromNative(buf []byte, datum interface{}) ([]byte, error) { + var someBytes []byte + switch d := datum.(type) { + case []byte: + someBytes = d + case string: + someBytes = []byte(d) + default: + return nil, fmt.Errorf("cannot encode binary bytes: expected: string; received: %T", datum) + } + buf, _ = longBinaryFromNative(buf, len(someBytes)) // only fails when given non integer + return append(buf, someBytes...), nil // append datum bytes +} + +//////////////////////////////////////// +// Text Decode +//////////////////////////////////////// + +func bytesNativeFromTextual(buf []byte) (interface{}, []byte, error) { + buflen := len(buf) + if buflen < 2 { + return nil, nil, fmt.Errorf("cannot decode textual bytes: %s", io.ErrShortBuffer) + } + if buf[0] != '"' { + return nil, nil, fmt.Errorf("cannot decode textual bytes: expected initial \"; found: %#U", buf[0]) + } + var newBytes []byte + var escaped bool + // Loop through bytes following initial double quote, but note we will + // return immediately when find unescaped double quote. + for i := 1; i < buflen; i++ { + b := buf[i] + if escaped { + escaped = false + if b2, ok := unescapeSpecialJSON(b); ok { + newBytes = append(newBytes, b2) + continue + } + if b == 'u' { + // NOTE: Need at least 4 more bytes to read uint16, but subtract + // 1 because do not want to count the trailing quote and + // subtract another 1 because already consumed u but have yet to + // increment i. + if i > buflen-6 { + return nil, nil, fmt.Errorf("cannot decode textual bytes: %s", io.ErrShortBuffer) + } + // NOTE: Avro bytes represent binary data, and do not + // necessarily represent text. Therefore, Avro bytes are not + // encoded in UTF-16. Each \u is followed by 4 hexadecimal + // digits, the first and second of which must be 0. + v, err := parseUint64FromHexSlice(buf[i+3 : i+5]) + if err != nil { + return nil, nil, fmt.Errorf("cannot decode textual bytes: %s", err) + } + i += 4 // absorb 4 characters: one 'u' and three of the digits + newBytes = append(newBytes, byte(v)) + continue + } + newBytes = append(newBytes, b) + continue + } + if b == '\\' { + escaped = true + continue + } + if b == '"' { + return newBytes, buf[i+1:], nil + } + newBytes = append(newBytes, b) + } + return nil, nil, fmt.Errorf("cannot decode textual bytes: expected final \"; found: %#U", buf[buflen-1]) +} + +func stringNativeFromTextual(buf []byte) (interface{}, []byte, error) { + buflen := len(buf) + if buflen < 2 { + return nil, nil, fmt.Errorf("cannot decode textual string: %s", io.ErrShortBuffer) + } + if buf[0] != '"' { + return nil, nil, fmt.Errorf("cannot decode textual string: expected initial \"; found: %#U", buf[0]) + } + var newBytes []byte + var escaped bool + // Loop through bytes following initial double quote, but note we will + // return immediately when find unescaped double quote. + for i := 1; i < buflen; i++ { + b := buf[i] + if escaped { + escaped = false + if b2, ok := unescapeSpecialJSON(b); ok { + newBytes = append(newBytes, b2) + continue + } + if b == 'u' { + // NOTE: Need at least 4 more bytes to read uint16, but subtract + // 1 because do not want to count the trailing quote and + // subtract another 1 because already consumed u but have yet to + // increment i. + if i > buflen-6 { + return nil, nil, fmt.Errorf("cannot decode textual string: %s", io.ErrShortBuffer) + } + v, err := parseUint64FromHexSlice(buf[i+1 : i+5]) + if err != nil { + return nil, nil, fmt.Errorf("cannot decode textual string: %s", err) + } + i += 4 // absorb 4 characters: one 'u' and three of the digits + + nbl := len(newBytes) + newBytes = append(newBytes, []byte{0, 0, 0, 0}...) // grow to make room for UTF-8 encoded rune + + r := rune(v) + if utf16.IsSurrogate(r) { + i++ // absorb final hexadecimal digit from previous value + + // Expect second half of surrogate pair + if i > buflen-6 || buf[i] != '\\' || buf[i+1] != 'u' { + return nil, nil, errors.New("cannot decode textual string: missing second half of surrogate pair") + } + + v, err = parseUint64FromHexSlice(buf[i+2 : i+6]) + if err != nil { + return nil, nil, fmt.Errorf("cannot decode textual string: %s", err) + } + i += 5 // absorb 5 characters: two for '\u', and 3 of the 4 digits + + // Get code point by combining high and low surrogate bits + r = utf16.DecodeRune(r, rune(v)) + } + + width := utf8.EncodeRune(newBytes[nbl:], r) // append UTF-8 encoded version of code point + newBytes = newBytes[:nbl+width] // trim off excess bytes + continue + } + newBytes = append(newBytes, b) + continue + } + if b == '\\' { + escaped = true + continue + } + if b == '"' { + return string(newBytes), buf[i+1:], nil + } + newBytes = append(newBytes, b) + } + if escaped { + return nil, nil, fmt.Errorf("cannot decode textual string: %s", io.ErrShortBuffer) + } + return nil, nil, fmt.Errorf("cannot decode textual string: expected final \"; found: %x", buf[buflen-1]) +} + +func unescapeUnicodeString(some string) (string, error) { + if some == "" { + return "", nil + } + buf := []byte(some) + buflen := len(buf) + var i int + var newBytes []byte + var escaped bool + // Loop through bytes following initial double quote, but note we will + // return immediately when find unescaped double quote. + for i = 0; i < buflen; i++ { + b := buf[i] + if escaped { + escaped = false + if b == 'u' { + // NOTE: Need at least 4 more bytes to read uint16, but subtract + // 1 because do not want to count the trailing quote and + // subtract another 1 because already consumed u but have yet to + // increment i. + if i > buflen-6 { + return "", fmt.Errorf("cannot replace escaped characters with UTF-8 equivalent: %s", io.ErrShortBuffer) + } + v, err := parseUint64FromHexSlice(buf[i+1 : i+5]) + if err != nil { + return "", fmt.Errorf("cannot replace escaped characters with UTF-8 equivalent: %s", err) + } + i += 4 // absorb 4 characters: one 'u' and three of the digits + + nbl := len(newBytes) + newBytes = append(newBytes, []byte{0, 0, 0, 0}...) // grow to make room for UTF-8 encoded rune + + r := rune(v) + if utf16.IsSurrogate(r) { + i++ // absorb final hexadecimal digit from previous value + + // Expect second half of surrogate pair + if i > buflen-6 || buf[i] != '\\' || buf[i+1] != 'u' { + return "", errors.New("cannot replace escaped characters with UTF-8 equivalent: missing second half of surrogate pair") + } + + v, err = parseUint64FromHexSlice(buf[i+2 : i+6]) + if err != nil { + return "", fmt.Errorf("cannot replace escaped characters with UTF-8 equivalents: %s", err) + } + i += 5 // absorb 5 characters: two for '\u', and 3 of the 4 digits + + // Get code point by combining high and low surrogate bits + r = utf16.DecodeRune(r, rune(v)) + } + + width := utf8.EncodeRune(newBytes[nbl:], r) // append UTF-8 encoded version of code point + newBytes = newBytes[:nbl+width] // trim off excess bytes + continue + } + newBytes = append(newBytes, b) + continue + } + if b == '\\' { + escaped = true + continue + } + newBytes = append(newBytes, b) + } + if escaped { + return "", fmt.Errorf("cannot replace escaped characters with UTF-8 equivalents: %s", io.ErrShortBuffer) + } + return string(newBytes), nil +} + +func parseUint64FromHexSlice(buf []byte) (uint64, error) { + var value uint64 + for _, b := range buf { + diff := uint64(b - '0') + if diff < 10 { + value = (value << 4) | diff + continue + } + b10 := b + 10 + diff = uint64(b10 - 'A') + if diff < 10 { + return 0, hex.InvalidByteError(b) + } + if diff < 16 { + value = (value << 4) | diff + continue + } + diff = uint64(b10 - 'a') + if diff < 10 { + return 0, hex.InvalidByteError(b) + } + if diff < 16 { + value = (value << 4) | diff + continue + } + return 0, hex.InvalidByteError(b) + } + return value, nil +} + +func unescapeSpecialJSON(b byte) (byte, bool) { + // NOTE: The following 8 special JSON characters must be escaped: + switch b { + case '"', '\\', '/': + return b, true + case 'b': + return '\b', true + case 'f': + return '\f', true + case 'n': + return '\n', true + case 'r': + return '\r', true + case 't': + return '\t', true + } + return b, false +} + +//////////////////////////////////////// +// Text Encode +//////////////////////////////////////// + +func bytesTextualFromNative(buf []byte, datum interface{}) ([]byte, error) { + var someBytes []byte + switch d := datum.(type) { + case []byte: + someBytes = d + case string: + someBytes = []byte(d) + default: + return nil, fmt.Errorf("cannot encode textual bytes: expected: []byte or string; received: %T", datum) + } + buf = append(buf, '"') // prefix buffer with double quote + for _, b := range someBytes { + if escaped, ok := escapeSpecialJSON(b); ok { + buf = append(buf, escaped...) + continue + } + if r := rune(b); r < utf8.RuneSelf && unicode.IsPrint(r) { + buf = append(buf, b) + continue + } + // This Code Point _could_ be encoded as a single byte, however, it's + // above standard ASCII range (b > 127), therefore must encode using its + // four-byte hexadecimal equivalent, which will always start with the + // high byte 00 + buf = appendUnicodeHex(buf, uint16(b)) + } + return append(buf, '"'), nil // postfix buffer with double quote +} + +func stringTextualFromNative(buf []byte, datum interface{}) ([]byte, error) { + var someString string + switch d := datum.(type) { + case []byte: + someString = string(d) + case string: + someString = d + default: + return nil, fmt.Errorf("cannot encode textual string: expected: []byte or string; received: %T", datum) + } + buf = append(buf, '"') // prefix buffer with double quote + for _, r := range someString { + if r < utf8.RuneSelf { + if escaped, ok := escapeSpecialJSON(byte(r)); ok { + buf = append(buf, escaped...) + continue + } + if unicode.IsPrint(r) { + buf = append(buf, byte(r)) + continue + } + } + // NOTE: Attempt to encode code point as UTF-16 surrogate pair + r1, r2 := utf16.EncodeRune(r) + if r1 != unicode.ReplacementChar || r2 != unicode.ReplacementChar { + // code point does require surrogate pair, and thus two uint16 values + buf = appendUnicodeHex(buf, uint16(r1)) + buf = appendUnicodeHex(buf, uint16(r2)) + continue + } + // Code Point does not require surrogate pair. + buf = appendUnicodeHex(buf, uint16(r)) + } + return append(buf, '"'), nil // postfix buffer with double quote +} + +func appendUnicodeHex(buf []byte, v uint16) []byte { + // Start with '\u' prefix: + buf = append(buf, sliceUnicode...) + // And tack on 4 hexadecimal digits: + buf = append(buf, hexDigits[(v&0xF000)>>12]) + buf = append(buf, hexDigits[(v&0xF00)>>8]) + buf = append(buf, hexDigits[(v&0xF0)>>4]) + buf = append(buf, hexDigits[(v&0xF)]) + return buf +} + +const hexDigits = "0123456789ABCDEF" + +func escapeSpecialJSON(b byte) ([]byte, bool) { + // NOTE: The following 8 special JSON characters must be escaped: + switch b { + case '"': + return sliceQuote, true + case '\\': + return sliceBackslash, true + case '/': + return sliceSlash, true + case '\b': + return sliceBackspace, true + case '\f': + return sliceFormfeed, true + case '\n': + return sliceNewline, true + case '\r': + return sliceCarriageReturn, true + case '\t': + return sliceTab, true + } + return nil, false +} + +// While slices in Go are never constants, we can initialize them once and reuse +// them many times. We define these slices at library load time and reuse them +// when encoding JSON. +var ( + sliceQuote = []byte("\\\"") + sliceBackslash = []byte("\\\\") + sliceSlash = []byte("\\/") + sliceBackspace = []byte("\\b") + sliceFormfeed = []byte("\\f") + sliceNewline = []byte("\\n") + sliceCarriageReturn = []byte("\\r") + sliceTab = []byte("\\t") + sliceUnicode = []byte("\\u") +) + +// DEBUG -- remove function prior to committing +func decodedStringFromJSON(buf []byte) (string, []byte, error) { + fmt.Fprintf(os.Stderr, "decodedStringFromJSON(%v)\n", buf) + buflen := len(buf) + if buflen < 2 { + return "", buf, fmt.Errorf("cannot decode string: %s", io.ErrShortBuffer) + } + if buf[0] != '"' { + return "", buf, fmt.Errorf("cannot decode string: expected initial '\"'; found: %#U", buf[0]) + } + var newBytes []byte + var escaped, ok bool + // Loop through bytes following initial double quote, but note we will + // return immediately when find unescaped double quote. + for i := 1; i < buflen; i++ { + b := buf[i] + if escaped { + escaped = false + if b, ok = unescapeSpecialJSON(b); ok { + newBytes = append(newBytes, b) + continue + } + if b == 'u' { + // NOTE: Need at least 4 more bytes to read uint16, but subtract + // 1 because do not want to count the trailing quote and + // subtract another 1 because already consumed u but have yet to + // increment i. + if i > buflen-6 { + return "", buf[i+1:], fmt.Errorf("cannot decode string: %s", io.ErrShortBuffer) + } + v, err := parseUint64FromHexSlice(buf[i+1 : i+5]) + if err != nil { + return "", buf[i+1:], fmt.Errorf("cannot decode string: %s", err) + } + i += 4 // absorb 4 characters: one 'u' and three of the digits + + nbl := len(newBytes) + newBytes = append(newBytes, 0, 0, 0, 0) // grow to make room for UTF-8 encoded rune + + r := rune(v) + if utf16.IsSurrogate(r) { + i++ // absorb final hexidecimal digit from previous value + + // Expect second half of surrogate pair + if i > buflen-6 || buf[i] != '\\' || buf[i+1] != 'u' { + return "", buf[i+1:], errors.New("cannot decode string: missing second half of surrogate pair") + } + + v, err = parseUint64FromHexSlice(buf[i+2 : i+6]) + if err != nil { + return "", buf[i+1:], fmt.Errorf("cannot decode string: cannot decode second half of surrogate pair: %s", err) + } + i += 5 // absorb 5 characters: two for '\u', and 3 of the 4 digits + + // Get code point by combining high and low surrogate bits + r = utf16.DecodeRune(r, rune(v)) + } + + width := utf8.EncodeRune(newBytes[nbl:], r) // append UTF-8 encoded version of code point + newBytes = newBytes[:nbl+width] // trim off excess bytes + continue + } + newBytes = append(newBytes, b) + continue + } + if b == '\\' { + escaped = true + continue + } + if b == '"' { + return string(newBytes), buf[i+1:], nil + } + newBytes = append(newBytes, b) + } + return "", buf, fmt.Errorf("cannot decode string: expected final '\"'; found: %#U", buf[buflen-1]) +} diff --git a/fork/goavro/bytes_test.go b/fork/goavro/bytes_test.go new file mode 100644 index 0000000..39eaa22 --- /dev/null +++ b/fork/goavro/bytes_test.go @@ -0,0 +1,207 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "encoding/json" + "strings" + "testing" +) + +func TestSchemaPrimitiveCodecBytes(t *testing.T) { + testSchemaPrimativeCodec(t, `"bytes"`) +} + +type TBytes []byte + +func TestPrimitiveBytesBinary(t *testing.T) { + testBinaryEncodeFailBadDatumType(t, `"bytes"`, 13) + testBinaryDecodeFailShortBuffer(t, `"bytes"`, nil) + testBinaryDecodeFailShortBuffer(t, `"bytes"`, []byte{2}) + testBinaryCodecPass(t, `"bytes"`, []byte(""), []byte("\x00")) + testBinaryCodecPass(t, `"bytes"`, []byte("some bytes"), []byte("\x14some bytes")) + testBinaryCodecPass(t, `"bytes"`, TBytes("some bytes"), []byte("\x14some bytes")) +} + +func TestPrimitiveBytesText(t *testing.T) { + testTextEncodeFailBadDatumType(t, `"bytes"`, 42) + testTextDecodeFailShortBuffer(t, `"bytes"`, []byte(``)) + testTextDecodeFailShortBuffer(t, `"bytes"`, []byte(`"`)) + testTextDecodeFail(t, `"bytes"`, []byte(`..`), "expected initial \"") + testTextDecodeFail(t, `"bytes"`, []byte(`".`), "expected final \"") + + testTextCodecPass(t, `"bytes"`, []byte(""), []byte("\"\"")) + testTextCodecPass(t, `"bytes"`, []byte("a"), []byte("\"a\"")) + testTextCodecPass(t, `"bytes"`, []byte("ab"), []byte("\"ab\"")) + testTextCodecPass(t, `"bytes"`, []byte("a\"b"), []byte("\"a\\\"b\"")) + testTextCodecPass(t, `"bytes"`, []byte("a\\b"), []byte("\"a\\\\b\"")) + testTextCodecPass(t, `"bytes"`, []byte("a/b"), []byte("\"a\\/b\"")) + + testTextCodecPass(t, `"bytes"`, []byte("a\bb"), []byte(`"a\bb"`)) + testTextCodecPass(t, `"bytes"`, []byte("a\fb"), []byte(`"a\fb"`)) + testTextCodecPass(t, `"bytes"`, []byte("a\nb"), []byte(`"a\nb"`)) + testTextCodecPass(t, `"bytes"`, []byte("a\rb"), []byte(`"a\rb"`)) + testTextCodecPass(t, `"bytes"`, []byte("a\tb"), []byte(`"a\tb"`)) + testTextCodecPass(t, `"bytes"`, []byte("a b"), []byte(`"a\tb"`)) // tab byte between a and b + + testTextDecodeFail(t, `"bytes"`, []byte("\"\\u\""), "short buffer") + testTextDecodeFail(t, `"bytes"`, []byte("\"\\u.\""), "short buffer") + testTextDecodeFail(t, `"bytes"`, []byte("\"\\u..\""), "short buffer") + testTextDecodeFail(t, `"bytes"`, []byte("\"\\u...\""), "short buffer") + + testTextDecodeFail(t, `"bytes"`, []byte("\"\\u////\""), "invalid byte") // < '0' + testTextDecodeFail(t, `"bytes"`, []byte("\"\\u::::\""), "invalid byte") // > '9' + testTextDecodeFail(t, `"bytes"`, []byte("\"\\u@@@@\""), "invalid byte") // < 'A' + testTextDecodeFail(t, `"bytes"`, []byte("\"\\uGGGG\""), "invalid byte") // > 'F' + testTextDecodeFail(t, `"bytes"`, []byte("\"\\u````\""), "invalid byte") // < 'a' + testTextDecodeFail(t, `"bytes"`, []byte("\"\\ugggg\""), "invalid byte") // > 'f' + + testTextCodecPass(t, `"bytes"`, []byte("⌘ "), []byte("\"\\u0001\\u00E2\\u008C\\u0098 \"")) + testTextCodecPass(t, `"bytes"`, []byte("😂"), []byte(`"\u00F0\u009F\u0098\u0082"`)) +} + +func TestSchemaPrimitiveStringCodec(t *testing.T) { + testSchemaPrimativeCodec(t, `"string"`) +} + +type TString string + +func TestPrimitiveStringBinary(t *testing.T) { + testBinaryEncodeFailBadDatumType(t, `"string"`, 42) + testBinaryDecodeFailShortBuffer(t, `"string"`, nil) + testBinaryDecodeFailShortBuffer(t, `"string"`, []byte{2}) + testBinaryCodecPass(t, `"string"`, "", []byte("\x00")) + testBinaryCodecPass(t, `"string"`, "some string", []byte("\x16some string")) + testBinaryCodecPass(t, `"string"`, TString("some string"), []byte("\x16some string")) +} + +func TestPrimitiveStringText(t *testing.T) { + testTextEncodeFailBadDatumType(t, `"string"`, 42) + testTextDecodeFailShortBuffer(t, `"string"`, []byte(``)) + testTextDecodeFailShortBuffer(t, `"string"`, []byte(`"`)) + testTextDecodeFail(t, `"string"`, []byte(`..`), "expected initial \"") + testTextDecodeFail(t, `"string"`, []byte(`".`), "expected final \"") + + testTextCodecPass(t, `"string"`, "", []byte("\"\"")) + testTextCodecPass(t, `"string"`, "a", []byte("\"a\"")) + testTextCodecPass(t, `"string"`, "ab", []byte("\"ab\"")) + testTextCodecPass(t, `"string"`, "a\"b", []byte("\"a\\\"b\"")) + testTextCodecPass(t, `"string"`, "a\\b", []byte("\"a\\\\b\"")) + testTextCodecPass(t, `"string"`, "a/b", []byte("\"a\\/b\"")) + + testTextCodecPass(t, `"string"`, "a\bb", []byte(`"a\bb"`)) + testTextCodecPass(t, `"string"`, "a\fb", []byte(`"a\fb"`)) + testTextCodecPass(t, `"string"`, "a\nb", []byte(`"a\nb"`)) + testTextCodecPass(t, `"string"`, "a\rb", []byte(`"a\rb"`)) + testTextCodecPass(t, `"string"`, "a\tb", []byte(`"a\tb"`)) + testTextCodecPass(t, `"string"`, "a b", []byte(`"a\tb"`)) // tab byte between a and b + + testTextDecodeFail(t, `"string"`, []byte("\"\\u\""), "short buffer") + testTextDecodeFail(t, `"string"`, []byte("\"\\u.\""), "short buffer") + testTextDecodeFail(t, `"string"`, []byte("\"\\u..\""), "short buffer") + testTextDecodeFail(t, `"string"`, []byte("\"\\u...\""), "short buffer") + + testTextDecodeFail(t, `"string"`, []byte("\"\\u////\""), "invalid byte") // < '0' + testTextDecodeFail(t, `"string"`, []byte("\"\\u::::\""), "invalid byte") // > '9' + testTextDecodeFail(t, `"string"`, []byte("\"\\u@@@@\""), "invalid byte") // < 'A' + testTextDecodeFail(t, `"string"`, []byte("\"\\uGGGG\""), "invalid byte") // > 'F' + testTextDecodeFail(t, `"string"`, []byte("\"\\u````\""), "invalid byte") // < 'a' + testTextDecodeFail(t, `"string"`, []byte("\"\\ugggg\""), "invalid byte") // > 'f' + + testTextCodecPass(t, `"string"`, "⌘ ", []byte("\"\\u0001\\u2318 \"")) + testTextCodecPass(t, `"string"`, "™ ", []byte("\"\\u0001\\u2122 \"")) + testTextCodecPass(t, `"string"`, "ℯ ", []byte("\"\\u0001\\u212F \"")) + testTextCodecPass(t, `"string"`, "😂 ", []byte("\"\\u0001\\uD83D\\uDE02 \"")) + + testTextDecodeFail(t, `"string"`, []byte("\"\\"), "short buffer") + testTextDecodeFail(t, `"string"`, []byte("\"\\uD83D\""), "surrogate pair") + testTextDecodeFail(t, `"string"`, []byte("\"\\uD83D\\u\""), "surrogate pair") + testTextDecodeFail(t, `"string"`, []byte("\"\\uD83D\\uD\""), "surrogate pair") + testTextDecodeFail(t, `"string"`, []byte("\"\\uD83D\\uDE\""), "surrogate pair") + testTextDecodeFail(t, `"string"`, []byte("\"\\uD83D\\uDE0\""), "invalid byte") +} + +func TestUnescapeUnicode(t *testing.T) { + checkGood := func(t *testing.T, argument, want string) { + got, err := unescapeUnicodeString(argument) + if err != nil { + t.Fatal(err) + } + if got != want { + t.Errorf("GOT: %q; WANT: %q", got, want) + } + } + + checkBad := func(t *testing.T, argument, want string) { + _, got := unescapeUnicodeString(argument) + if got == nil || !strings.Contains(got.Error(), want) { + t.Errorf("GOT: %v; WANT: %v", got, want) + } + } + + checkBad(t, "\\u0000", "short buffer") + checkBad(t, "\\uinvalid", "invalid byte") + checkBad(t, "\\ud83d\\ude0", "missing second half of surrogate pair") + checkBad(t, "\\ud83d\\uinvalid", "invalid byte") + checkBad(t, "\\", "short buffer") + checkGood(t, "", "") + checkGood(t, "\\\\", "\\") + checkGood(t, "\u0041\u0062\u0063", "Abc") + checkGood(t, "\u0001\\uD83D\\uDE02 ", "😂 ") + checkGood(t, "Hello, \u0022World!\"", "Hello, \"World!\"") + checkGood(t, "\u263a\ufe0f", "☺️") + checkGood(t, "\u65e5\u672c\u8a9e", "日本語") +} + +func TestJSONUnmarshalStrings(t *testing.T) { + cases := []struct { + arg string + want string + }{ + {arg: `"A1"`, want: "A1"}, + {arg: `"\u0042\u0032"`, want: "B2"}, // backslashes have no meaning in back-tick string constant + } + + for _, c := range cases { + var raw interface{} + if err := json.Unmarshal([]byte(c.arg), &raw); err != nil { + t.Errorf("CASE: %s; ERROR: %s", c.arg, err) + return + } + got, ok := raw.(string) + if !ok { + t.Errorf("CASE: %s; GOT: %T; WANT: string", c.arg, got) + return + } + if got != c.want { + t.Errorf("GOT: %s; WANT: %q", got, c.want) + } + } +} + +func TestBytesCodecAcceptsString(t *testing.T) { + schema := `{"type":"bytes"}` + t.Run("binary", func(t *testing.T) { + testBinaryEncodePass(t, schema, "abcd", []byte("\x08abcd")) + }) + t.Run("text", func(t *testing.T) { + testTextEncodePass(t, schema, "abcd", []byte(`"abcd"`)) + }) +} + +func TestStringCodecAcceptsBytes(t *testing.T) { + schema := `{"type":"string"}` + t.Run("binary", func(t *testing.T) { + testBinaryEncodePass(t, schema, []byte("abcd"), []byte("\x08abcd")) + }) + t.Run("text", func(t *testing.T) { + testTextEncodePass(t, schema, []byte("abcd"), []byte(`"abcd"`)) + }) +} diff --git a/fork/goavro/canonical.go b/fork/goavro/canonical.go new file mode 100644 index 0000000..32ac608 --- /dev/null +++ b/fork/goavro/canonical.go @@ -0,0 +1,188 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "fmt" + "sort" + "strconv" + "strings" +) + +// pcfProcessor is a function type that given a parsed JSON object, returns its +// Parsing Canonical Form according to the Avro specification. +type pcfProcessor func(s interface{}) (string, error) + +// parsingCanonialForm returns the "Parsing Canonical Form" (pcf) for a parsed +// JSON structure of a valid Avro schema, or an error describing the schema +// error. +func parsingCanonicalForm(schema interface{}, parentNamespace string, typeLookup map[string]string) (string, error) { + switch val := schema.(type) { + case map[string]interface{}: + // JSON objects are decoded as a map of strings to empty interfaces + return pcfObject(val, parentNamespace, typeLookup) + case []interface{}: + // JSON arrays are decoded as a slice of empty interfaces + return pcfArray(val, parentNamespace, typeLookup) + case string: + // JSON string values are decoded as a Go string + return pcfString(val, typeLookup) + case float64: + // JSON numerical values are decoded as Go float64 + return pcfNumber(val) + default: + return "", fmt.Errorf("cannot parse schema with invalid schema type; ought to be map[string]interface{}, []interface{}, string, or float64; received: %T: %v", schema, schema) + } +} + +// pcfNumber returns the parsing canonical form for a numerical value. +func pcfNumber(val float64) (string, error) { + return strconv.FormatFloat(val, 'g', -1, 64), nil +} + +// pcfString returns the parsing canonical form for a string value. +func pcfString(val string, typeLookup map[string]string) (string, error) { + if canonicalName, ok := typeLookup[val]; ok { + return `"` + canonicalName + `"`, nil + } + return `"` + val + `"`, nil +} + +// pcfArray returns the parsing canonical form for a JSON array. +func pcfArray(val []interface{}, parentNamespace string, typeLookup map[string]string) (string, error) { + items := make([]string, len(val)) + for i, el := range val { + p, err := parsingCanonicalForm(el, parentNamespace, typeLookup) + if err != nil { + return "", err + } + items[i] = p + } + return "[" + strings.Join(items, ",") + "]", nil +} + +// pcfObject returns the parsing canonical form for a JSON object. +func pcfObject(jsonMap map[string]interface{}, parentNamespace string, typeLookup map[string]string) (string, error) { + pairs := make(stringPairs, 0, len(jsonMap)) + + // Remember the namespace to fully qualify names later + var namespace string + if namespaceJSON, ok := jsonMap["namespace"]; ok { + if namespaceStr, ok := namespaceJSON.(string); ok { + // and it's value is string (otherwise invalid schema) + if parentNamespace == "" { + namespace = namespaceStr + } else { + namespace = parentNamespace + "." + namespaceStr + } + parentNamespace = namespace + } + } else if objectType, ok := jsonMap["type"]; ok && (objectType == "record" || objectType == "enum" || objectType == "fixed") { + namespace = parentNamespace + } + + for k, v := range jsonMap { + + // Reduce primitive schemas to their simple form. + if len(jsonMap) == 1 && k == "type" { + if t, ok := v.(string); ok { + return "\"" + t + "\"", nil + } + } + + // Only keep relevant attributes (strip 'doc', 'alias', 'namespace') + if _, ok := fieldOrder[k]; !ok { + continue + } + + // Add namespace to a non-qualified name. + if k == "name" && namespace != "" { + // Check if the name isn't already qualified. + if t, ok := v.(string); ok && !strings.ContainsRune(t, '.') { + v = namespace + "." + t + typeLookup[t] = v.(string) + } + } + + // Only fixed type allows size, and we must convert a string size to a + // float. + if k == "size" { + if s, ok := v.(string); ok { + s, err := strconv.ParseUint(s, 10, 0) + if err != nil { + // should never get here because already validated schema + return "", fmt.Errorf("Fixed size ought to be number greater than zero: %v", s) + } + v = float64(s) + } + } + + pk, err := parsingCanonicalForm(k, parentNamespace, typeLookup) + if err != nil { + return "", err + } + pv, err := parsingCanonicalForm(v, parentNamespace, typeLookup) + if err != nil { + return "", err + } + + pairs = append(pairs, stringPair{k, pk + ":" + pv}) + } + + // Sort keys by their order in specification. + sort.Sort(byAvroFieldOrder(pairs)) + return "{" + strings.Join(pairs.Bs(), ",") + "}", nil +} + +// stringPair represents a pair of string values. +type stringPair struct { + A string + B string +} + +// stringPairs is a sortable slice of pairs of strings. +type stringPairs []stringPair + +// Bs returns an array of second values of an array of pairs. +func (sp *stringPairs) Bs() []string { + items := make([]string, len(*sp)) + for i, el := range *sp { + items[i] = el.B + } + return items +} + +// fieldOrder defines fields that show up in canonical schema and specifies +// their precedence. +var fieldOrder = map[string]int{ + "name": 1, + "type": 2, + "fields": 3, + "symbols": 4, + "items": 5, + "values": 6, + "size": 7, +} + +// byAvroFieldOrder is equipped with a sort order of fields according to the +// specification. +type byAvroFieldOrder []stringPair + +func (s byAvroFieldOrder) Len() int { + return len(s) +} + +func (s byAvroFieldOrder) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s byAvroFieldOrder) Less(i, j int) bool { + return fieldOrder[s[i].A] < fieldOrder[s[j].A] +} diff --git a/fork/goavro/canonical_test.go b/fork/goavro/canonical_test.go new file mode 100644 index 0000000..811e0eb --- /dev/null +++ b/fork/goavro/canonical_test.go @@ -0,0 +1,287 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "testing" +) + +func TestCanonicalSchema(t *testing.T) { + // Test cases are taken from the reference implementation here: + // https://github.com/apache/avro/blob/master/share/test/data/schema-tests.txt + + cases := []struct { + Schema string + Canonical string + }{ + { + Schema: `"null"`, + Canonical: `"null"`, + }, + { + Schema: `{"type":"null"}`, + Canonical: `"null"`, + }, + { + Schema: `"boolean"`, + Canonical: `"boolean"`, + }, + { + Schema: `{"type":"boolean"}`, + Canonical: `"boolean"`, + }, + { + Schema: `"int"`, + Canonical: `"int"`, + }, + { + Schema: `{"type":"int"}`, + Canonical: `"int"`, + }, + { + Schema: `"long"`, + Canonical: `"long"`, + }, + { + Schema: `{"type":"long"}`, + Canonical: `"long"`, + }, + { + Schema: `"float"`, + Canonical: `"float"`, + }, + { + Schema: `{"type":"float"}`, + Canonical: `"float"`, + }, + { + Schema: `"double"`, + Canonical: `"double"`, + }, + { + Schema: `{"type":"double"}`, + Canonical: `"double"`, + }, + { + Schema: `"bytes"`, + Canonical: `"bytes"`, + }, + { + Schema: `{"type":"bytes"}`, + Canonical: `"bytes"`, + }, + { + Schema: `"string"`, + Canonical: `"string"`, + }, + { + Schema: `{"type":"string"}`, + Canonical: `"string"`, + }, + /* + // Supported by the reference implementation but not by goavro at this point + { + Schema: "[ ]", + Canonical: "[]", + }, + */ + { + Schema: `[ "int" ]`, + Canonical: `["int"]`, + }, + { + Schema: `[ "int" , {"type":"boolean"} ]`, + Canonical: `["int","boolean"]`, + }, + + // The following 7 test cases differ from the reference implementation since goavro doesn't + // currently support empty fields array. A field name "dummy" is added since these tests are + // testing other aspects of canonicalization than empty field array. + { + Schema: `{"fields":[{"name":"dummy","type":"int"}], "type":"record", "name":"foo"}`, + Canonical: `{"name":"foo","type":"record","fields":[{"name":"dummy","type":"int"}]}`, + }, + { + Schema: `{"fields":[{"name":"dummy","type":"int"}], "type":"record", "name":"foo", "namespace":"x.y"}`, + Canonical: `{"name":"x.y.foo","type":"record","fields":[{"name":"dummy","type":"int"}]}`, + }, + { + Schema: `{"fields":[{"name":"dummy","type":"int"}], "type":"record", "name":"foo", "namespace":"x.y"}`, + Canonical: `{"name":"x.y.foo","type":"record","fields":[{"name":"dummy","type":"int"}]}`, + }, + { + Schema: `{"fields":[{"name":"dummy","type":"int"}], "type":"record", "name":"a.b.foo", "namespace":"x.y"}`, + Canonical: `{"name":"a.b.foo","type":"record","fields":[{"name":"dummy","type":"int"}]}`, + }, + { + Schema: `{"fields":[{"name":"dummy","type":"int"}], "type":"record", "name":"foo", "doc":"Useful info"}`, + Canonical: `{"name":"foo","type":"record","fields":[{"name":"dummy","type":"int"}]}`, + }, + { + Schema: `{"fields":[{"name":"dummy","type":"int"}], "type":"record", "name":"foo", "aliases":["foo","bar"]}`, + Canonical: `{"name":"foo","type":"record","fields":[{"name":"dummy","type":"int"}]}`, + }, + { + Schema: `{"fields":[{"name":"dummy","type":"int"}], "type":"record", "name":"foo", "doc":"foo", "aliases":["foo","bar"]}`, + Canonical: `{"name":"foo","type":"record","fields":[{"name":"dummy","type":"int"}]}`, + }, + + { + Schema: `{"fields":[{"type":{"type":"boolean"}, "name":"f1"}], "type":"record", "name":"foo"}`, + Canonical: `{"name":"foo","type":"record","fields":[{"name":"f1","type":"boolean"}]}`, + }, + { + Schema: `{"fields": [ + {"type": "boolean", "aliases": [], "name": "f1", "default": true}, + {"order": "descending", "name": "f2", "doc": "Hello", "type": "int"} + ], + "type": "record", + "name": "foo"}`, + Canonical: `{"name":"foo","type":"record","fields":[{"name":"f1","type":"boolean"},{"name":"f2","type":"int"}]}`, + }, + { + Schema: `{"type":"enum", "name":"foo", "symbols":["A1"]}`, + Canonical: `{"name":"foo","type":"enum","symbols":["A1"]}`, + }, + { + Schema: `{"namespace":"x.y.z", "type":"enum", "name":"foo", "doc":"foo bar", "symbols":["A1", "A2"]}`, + Canonical: `{"name":"x.y.z.foo","type":"enum","symbols":["A1","A2"]}`, + }, + { + Schema: `{"type":"record", "name":"a.b.foo", "namespace":"x.y", "fields":[{"name":"bar","type":"enum","symbols":["A1","A2"]}]}`, + Canonical: `{"name":"a.b.foo","type":"record","fields":[{"name":"x.y.bar","type":"enum","symbols":["A1","A2"]}]}`, + }, + { + Schema: `{"type":"record", "name":"foo", "namespace":"x.y", "fields":[{"name":"bar","type":"enum","symbols":["A1","A2"]}]}`, + Canonical: `{"name":"x.y.foo","type":"record","fields":[{"name":"x.y.bar","type":"enum","symbols":["A1","A2"]}]}`, + }, + { + Schema: `{"type":"record", "name":"foo", "namespace":"x.y", "fields":[{"name":"a.b.bar","type":"enum","symbols":["A1","A2"]}]}`, + Canonical: `{"name":"x.y.foo","type":"record","fields":[{"name":"a.b.bar","type":"enum","symbols":["A1","A2"]}]}`, + }, + { + Schema: `{"name":"foo","type":"fixed","size":15}`, + Canonical: `{"name":"foo","type":"fixed","size":15}`, + }, + { + Schema: `{"namespace":"x.y.z", "type":"fixed", "name":"foo", "doc":"foo bar", "size":32}`, + Canonical: `{"name":"x.y.z.foo","type":"fixed","size":32}`, + }, + { + Schema: `{"type":"record", "name":"foo", "namespace":"x.y", "fields":[{"name":"bar","type":"fixed", "doc":"foo bar", "size":32}]}`, + Canonical: `{"name":"x.y.foo","type":"record","fields":[{"name":"x.y.bar","type":"fixed","size":32}]}`, + }, + { + Schema: `{"type":"record", "name":"foo", "namespace":"x.y", "fields":[{"name":"a.b.bar","type":"fixed", "doc":"foo bar", "size":32}]}`, + Canonical: `{"name":"x.y.foo","type":"record","fields":[{"name":"a.b.bar","type":"fixed","size":32}]}`, + }, + { + Schema: `{ "items":{"type":"null"}, "type":"array"}`, + Canonical: `{"type":"array","items":"null"}`, + }, + { + Schema: `{ "values":"string", "type":"map"}`, + Canonical: `{"type":"map","values":"string"}`, + }, + { + Schema: ` {"name":"PigValue","type":"record", + "fields":[{"name":"value", "type":["null", "int", "long", "PigValue"]}]}`, + Canonical: `{"name":"PigValue","type":"record","fields":[{"name":"value","type":["null","int","long","PigValue"]}]}`, + }, + + // [INTEGERS] Eliminate quotes around and any leading zeros in front of + // JSON integer literals (which appear in the size attributes of fixed + // schemas). + { + Schema: `{"size":"15","type":"fixed","name":"foo"}`, + Canonical: `{"name":"foo","type":"fixed","size":15}`, + }, + + // [STRINGS] For all JSON string literals in the schema text, replace + // any escaped characters (e.g., \uXXXX escapes) with their UTF-8 + // equivalents. + { + // primitive + Schema: `"\u0069\u006e\u0074"`, + Canonical: `"int"`, + }, + { + // primitive wrapped in JSON object + Schema: `{"type":"\u0069\u006e\u0074"}`, + Canonical: `"int"`, + }, + { + // array items + Schema: `{"type":"array","items":"\u0069\u006e\u0074"}`, + Canonical: `{"type":"array","items":"int"}`, + }, + { + // enum symbols + Schema: `{"type":"enum","symbols":["\u0047\u006f","\u0041\u0076\u0072\u006f"],"name":"\u0046\u006f\u006f"}`, + Canonical: `{"name":"Foo","type":"enum","symbols":["Go","Avro"]}`, + }, + { + // fixed name + Schema: `{"size":16,"type":"fixed","name":"\u0046\u006f\u006f"}`, + Canonical: `{"name":"Foo","type":"fixed","size":16}`, + }, + { + // map values + Schema: `{"values":"\u0069\u006e\u0074","type":"map"}`, + Canonical: `{"type":"map","values":"int"}`, + }, + { + // record name + Schema: `{"fields":[{"name":"hi","type":"int"}], "type":"record", "name":"\u0046\u006f\u006f", "namespace":"x.y"}`, + Canonical: `{"name":"x.y.Foo","type":"record","fields":[{"name":"hi","type":"int"}]}`, + }, + { + // record namespace + Schema: `{"fields":[{"name":"hi","type":"int"}], "type":"record", "name":"Foo", "namespace":"\u0078\u002e\u0079"}`, + Canonical: `{"name":"x.y.Foo","type":"record","fields":[{"name":"hi","type":"int"}]}`, + }, + { + // record field name + Schema: `{"fields":[{"name":"\u0068\u0069","type":"int"}], "type":"record", "name":"Foo", "namespace":"x.y"}`, + Canonical: `{"name":"x.y.Foo","type":"record","fields":[{"name":"hi","type":"int"}]}`, + }, + { + // record field type + Schema: `{"fields":[{"name":"hi","type":"\u0069\u006e\u0074"}], "type":"record", "name":"Foo", "namespace":"x.y"}`, + Canonical: `{"name":"x.y.Foo","type":"record","fields":[{"name":"hi","type":"int"}]}`, + }, + { + // union children + Schema: `["\u006e\u0075\u006c\u006c","\u0069\u006e\u0074"]`, + Canonical: `["null","int"]`, + }, + { + // propagate namespace "bar" to subtype "baz", i.e., "bar.baz" + Schema: `{"type":"record","name":"foo","namespace":"bar","fields":[{"type":"record","name":"baz","fields":[{"name":"hi","type":"int"}]}]}`, + Canonical: `{"name":"bar.foo","type":"record","fields":[{"name":"bar.baz","type":"record","fields":[{"name":"hi","type":"int"}]}]}`, + }, + { + // replace the second reference to type "baz" in bye to "bar.baz" + Schema: `{"type":"record","name":"foo","namespace":"bar","fields":[{"type":"record","name":"baz","fields":[{"name":"hi","type":"int"}]},{"name":"bye", "type":["null","baz"]}]}`, + Canonical: `{"name":"bar.foo","type":"record","fields":[{"name":"bar.baz","type":"record","fields":[{"name":"hi","type":"int"}]},{"name":"bye","type":["null","bar.baz"]}]}`, + }, + } + + for _, c := range cases { + codec, err := NewCodec(c.Schema) + if err != nil { + t.Errorf("Unable to create codec for schema: %s\nwith error: %s", c.Schema, err) + } else { + if got, want := codec.CanonicalSchema(), c.Canonical; got != want { + t.Errorf("Test failed for schema: %s\n\tgot canonical:\t\t%s\n\texpected canonical:\t%s", c.Schema, got, want) + } + } + } +} diff --git a/fork/goavro/codec.go b/fork/goavro/codec.go new file mode 100644 index 0000000..72093ea --- /dev/null +++ b/fork/goavro/codec.go @@ -0,0 +1,699 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "fmt" + "math" + "strconv" +) + +var ( + // MaxBlockCount is the maximum number of data items allowed in a single + // block that will be decoded from a binary stream, whether when reading + // blocks to decode an array or a map, or when reading blocks from an OCF + // stream. This check is to ensure decoding binary data will not cause the + // library to over allocate RAM, potentially creating a denial of service on + // the system. + // + // If a particular application needs to decode binary Avro data that + // potentially has more data items in a single block, then this variable may + // be modified at your discretion. + MaxBlockCount = int64(math.MaxInt32) + + // MaxBlockSize is the maximum number of bytes that will be allocated for a + // single block of data items when decoding from a binary stream. This check + // is to ensure decoding binary data will not cause the library to over + // allocate RAM, potentially creating a denial of service on the system. + // + // If a particular application needs to decode binary Avro data that + // potentially has more bytes in a single block, then this variable may be + // modified at your discretion. + MaxBlockSize = int64(math.MaxInt32) +) + +// Codec supports decoding binary and text Avro data to Go native data types, +// and conversely encoding Go native data types to binary or text Avro data. A +// Codec is created as a stateless structure that can be safely used in multiple +// go routines simultaneously. +type Codec struct { + soeHeader []byte // single-object-encoding header + schemaOriginal string + schemaCanonical string + typeName *name + isNamedType bool + + nativeFromTextual func([]byte) (interface{}, []byte, error) + binaryFromNative func([]byte, interface{}) ([]byte, error) + nativeFromBinary func([]byte) (interface{}, []byte, error) + textualFromNative func([]byte, interface{}) ([]byte, error) + + Rabin uint64 +} + +type ConvertBuild func(f func([]byte, interface{}) ([]byte, error)) func([]byte, interface{}) ([]byte, error) + +// codecBuilder holds the 3 kinds of codec builders so they can be +// replaced if needed +// and so they can be passed down the call stack during codec building +type codecBuilder struct { + mapBuilder func(converters map[string]ConvertBuild, st map[string]*Codec, enclosingNamespace string, schemaMap map[string]interface{}, cb *codecBuilder) (*Codec, error) + stringBuilder func(converters map[string]ConvertBuild, st map[string]*Codec, enclosingNamespace string, typeName string, schemaMap map[string]interface{}, cb *codecBuilder) (*Codec, error) + sliceBuilder func(converters map[string]ConvertBuild, st map[string]*Codec, enclosingNamespace string, schemaArray []interface{}, cb *codecBuilder) (*Codec, error) +} + +// NewCodec returns a Codec used to translate between a byte slice of either +// binary or textual Avro data and native Go data. +// +// Creating a `Codec` is fast, but ought to be performed exactly once per Avro +// schema to process. Once a `Codec` is created, it may be used multiple times +// to convert data between native form and binary Avro representation, or +// between native form and textual Avro representation. +// +// A particular `Codec` can work with only one Avro schema. However, +// there is no practical limit to how many `Codec`s may be created and +// used in a program. Internally a `Codec` is merely a named tuple of +// four function pointers, and maintains no runtime state that is mutated +// after instantiation. In other words, `Codec`s may be safely used by +// many go routines simultaneously, as your program requires. +// +// codec, err := goavro.NewCodec(` +// { +// "type": "record", +// "name": "LongList", +// "fields" : [ +// {"name": "next", "type": ["null", "LongList"], "default": null} +// ] +// }`) +// if err != nil { +// fmt.Println(err) +// } +func NewCodec(schemaSpecification string) (*Codec, error) { + return NewCodecFrom(schemaSpecification, &codecBuilder{ + buildCodecForTypeDescribedByMap, + buildCodecForTypeDescribedByString, + buildCodecForTypeDescribedBySlice, + }, nil) +} + +func NewCodecWithConverters(schemaSpecification string, converters map[string]ConvertBuild) (*Codec, error) { + return NewCodecFrom(schemaSpecification, &codecBuilder{ + buildCodecForTypeDescribedByMap, + buildCodecForTypeDescribedByString, + buildCodecForTypeDescribedBySlice, + }, converters) +} + +func NewCodecForStandardJSON(schemaSpecification string) (*Codec, error) { + return NewCodecFrom(schemaSpecification, &codecBuilder{ + buildCodecForTypeDescribedByMap, + buildCodecForTypeDescribedByString, + buildCodecForTypeDescribedBySliceJSON, + }, nil, + ) +} + +func NewCodecForStandardJSONWithConverters(schemaSpecification string, converters map[string]ConvertBuild) (*Codec, error) { + return NewCodecFrom(schemaSpecification, &codecBuilder{ + buildCodecForTypeDescribedByMap, + buildCodecForTypeDescribedByString, + buildCodecForTypeDescribedBySliceJSON, + }, converters, + ) +} + +func NewCodecFrom(schemaSpecification string, cb *codecBuilder, converters map[string]ConvertBuild) (*Codec, error) { + var schema interface{} + + if err := json.Unmarshal([]byte(schemaSpecification), &schema); err != nil { + return nil, fmt.Errorf("cannot unmarshal schema JSON: %s", err) + } + + // bootstrap a symbol table with primitive type codecs for the new codec + st := newSymbolTable() + + c, err := buildCodec(converters, st, nullNamespace, schema, cb) + if err != nil { + return nil, err + } + c.schemaCanonical, err = parsingCanonicalForm(schema, "", make(map[string]string)) + if err != nil { + return nil, err // should not get here because schema was validated above + } + + c.Rabin = rabin([]byte(c.schemaCanonical)) + c.soeHeader = []byte{0xC3, 0x01, 0, 0, 0, 0, 0, 0, 0, 0} + binary.LittleEndian.PutUint64(c.soeHeader[2:], c.Rabin) + + c.schemaOriginal = schemaSpecification + return c, nil +} + +func newSymbolTable() map[string]*Codec { + return map[string]*Codec{ + "boolean": { + typeName: &name{"boolean", nullNamespace}, + schemaOriginal: "boolean", + schemaCanonical: "boolean", + binaryFromNative: booleanBinaryFromNative, + nativeFromBinary: booleanNativeFromBinary, + nativeFromTextual: booleanNativeFromTextual, + textualFromNative: booleanTextualFromNative, + }, + "bytes": { + typeName: &name{"bytes", nullNamespace}, + schemaOriginal: "bytes", + schemaCanonical: "bytes", + binaryFromNative: bytesBinaryFromNative, + nativeFromBinary: bytesNativeFromBinary, + nativeFromTextual: bytesNativeFromTextual, + textualFromNative: bytesTextualFromNative, + }, + "double": { + typeName: &name{"double", nullNamespace}, + schemaOriginal: "double", + schemaCanonical: "double", + binaryFromNative: doubleBinaryFromNative, + nativeFromBinary: doubleNativeFromBinary, + nativeFromTextual: doubleNativeFromTextual, + textualFromNative: doubleTextualFromNative, + }, + "float": { + typeName: &name{"float", nullNamespace}, + schemaOriginal: "float", + schemaCanonical: "float", + binaryFromNative: floatBinaryFromNative, + nativeFromBinary: floatNativeFromBinary, + nativeFromTextual: floatNativeFromTextual, + textualFromNative: floatTextualFromNative, + }, + "int": { + typeName: &name{"int", nullNamespace}, + schemaOriginal: "int", + schemaCanonical: "int", + binaryFromNative: intBinaryFromNative, + nativeFromBinary: intNativeFromBinary, + nativeFromTextual: intNativeFromTextual, + textualFromNative: intTextualFromNative, + }, + "long": { + typeName: &name{"long", nullNamespace}, + schemaOriginal: "long", + schemaCanonical: "long", + binaryFromNative: longBinaryFromNative, + nativeFromBinary: longNativeFromBinary, + nativeFromTextual: longNativeFromTextual, + textualFromNative: longTextualFromNative, + }, + "null": { + typeName: &name{"null", nullNamespace}, + schemaOriginal: "null", + schemaCanonical: "null", + binaryFromNative: nullBinaryFromNative, + nativeFromBinary: nullNativeFromBinary, + nativeFromTextual: nullNativeFromTextual, + textualFromNative: nullTextualFromNative, + }, + "string": { + typeName: &name{"string", nullNamespace}, + schemaOriginal: "string", + schemaCanonical: "string", + binaryFromNative: bytesBinaryFromNative, + nativeFromBinary: stringNativeFromBinary, + nativeFromTextual: stringNativeFromTextual, + textualFromNative: stringTextualFromNative, + }, + // Start of compiled logical types using format typeName.logicalType where there is + // no dependence on schema. + "long.timestamp-millis": { + typeName: &name{"long.timestamp-millis", nullNamespace}, + schemaOriginal: "long", + schemaCanonical: "long", + nativeFromTextual: nativeFromTimeStampMillis(longNativeFromTextual), + binaryFromNative: timeStampMillisFromNative(longBinaryFromNative), + nativeFromBinary: nativeFromTimeStampMillis(longNativeFromBinary), + textualFromNative: timeStampMillisFromNative(longTextualFromNative), + }, + "long.timestamp-micros": { + typeName: &name{"long.timestamp-micros", nullNamespace}, + schemaOriginal: "long", + schemaCanonical: "long", + nativeFromTextual: nativeFromTimeStampMicros(longNativeFromTextual), + binaryFromNative: timeStampMicrosFromNative(longBinaryFromNative), + nativeFromBinary: nativeFromTimeStampMicros(longNativeFromBinary), + textualFromNative: timeStampMicrosFromNative(longTextualFromNative), + }, + "int.time-millis": { + typeName: &name{"int.time-millis", nullNamespace}, + schemaOriginal: "int", + schemaCanonical: "int", + nativeFromTextual: nativeFromTimeMillis(intNativeFromTextual), + binaryFromNative: timeMillisFromNative(intBinaryFromNative), + nativeFromBinary: nativeFromTimeMillis(intNativeFromBinary), + textualFromNative: timeMillisFromNative(intTextualFromNative), + }, + "long.time-micros": { + typeName: &name{"long.time-micros", nullNamespace}, + schemaOriginal: "long", + schemaCanonical: "long", + nativeFromTextual: nativeFromTimeMicros(longNativeFromTextual), + binaryFromNative: timeMicrosFromNative(longBinaryFromNative), + nativeFromBinary: nativeFromTimeMicros(longNativeFromBinary), + textualFromNative: timeMicrosFromNative(longTextualFromNative), + }, + "int.date": { + typeName: &name{"int.date", nullNamespace}, + schemaOriginal: "int", + schemaCanonical: "int", + nativeFromTextual: nativeFromDate(intNativeFromTextual), + binaryFromNative: dateFromNative(intBinaryFromNative), + nativeFromBinary: nativeFromDate(intNativeFromBinary), + textualFromNative: dateFromNative(intTextualFromNative), + }, + } +} + +// BinaryFromNative appends the binary encoded byte slice representation of the +// provided native datum value to the provided byte slice in accordance with the +// Avro schema supplied when creating the Codec. It is supplied a byte slice to +// which to append the binary encoded data along with the actual data to encode. +// On success, it returns a new byte slice with the encoded bytes appended, and +// a nil error value. On error, it returns the original byte slice, and the +// error message. +// +// func ExampleBinaryFromNative() { +// codec, err := goavro.NewCodec(` +// { +// "type": "record", +// "name": "LongList", +// "fields" : [ +// {"name": "next", "type": ["null", "LongList"], "default": null} +// ] +// }`) +// if err != nil { +// fmt.Println(err) +// } +// +// // Convert native Go form to binary Avro data +// binary, err := codec.BinaryFromNative(nil, map[string]interface{}{ +// "next": map[string]interface{}{ +// "LongList": map[string]interface{}{ +// "next": map[string]interface{}{ +// "LongList": map[string]interface{}{ +// // NOTE: May omit fields when using default value +// }, +// }, +// }, +// }, +// }) +// if err != nil { +// fmt.Println(err) +// } +// +// fmt.Printf("%#v", binary) +// // Output: []byte{0x2, 0x2, 0x0} +// } +func (c *Codec) BinaryFromNative(buf []byte, datum interface{}) ([]byte, error) { + newBuf, err := c.binaryFromNative(buf, datum) + if err != nil { + return buf, err // if error, return original byte slice + } + return newBuf, nil +} + +// NativeFromBinary returns a native datum value from the binary encoded byte +// slice in accordance with the Avro schema supplied when creating the Codec. On +// success, it returns the decoded datum, a byte slice containing the remaining +// undecoded bytes, and a nil error value. On error, it returns nil for +// the datum value, the original byte slice, and the error message. +// +// func ExampleNativeFromBinary() { +// codec, err := goavro.NewCodec(` +// { +// "type": "record", +// "name": "LongList", +// "fields" : [ +// {"name": "next", "type": ["null", "LongList"], "default": null} +// ] +// }`) +// if err != nil { +// fmt.Println(err) +// } +// +// // Convert native Go form to binary Avro data +// binary := []byte{0x2, 0x2, 0x0} +// +// native, _, err := codec.NativeFromBinary(binary) +// if err != nil { +// fmt.Println(err) +// } +// +// fmt.Printf("%v", native) +// // Output: map[next:map[LongList:map[next:map[LongList:map[next:]]]]] +// } +func (c *Codec) NativeFromBinary(buf []byte) (interface{}, []byte, error) { + value, newBuf, err := c.nativeFromBinary(buf) + if err != nil { + return nil, buf, err // if error, return original byte slice + } + return value, newBuf, nil +} + +// NativeFromSingle converts Avro data from Single-Object-Encoded format from +// the provided byte slice to Go native data types in accordance with the Avro +// schema supplied when creating the Codec. On success, it returns the decoded +// datum, along with a new byte slice with the decoded bytes consumed, and a nil +// error value. On error, it returns nil for the datum value, the original byte +// slice, and the error message. +// +// func decode(codec *goavro.Codec, buf []byte) error { +// datum, _, err := codec.NativeFromSingle(buf) +// if err != nil { +// return err +// } +// _, err = fmt.Println(datum) +// return err +// } +func (c *Codec) NativeFromSingle(buf []byte) (interface{}, []byte, error) { + fingerprint, newBuf, err := FingerprintFromSOE(buf) + if err != nil { + return nil, buf, err + } + if !bytes.Equal(buf[:len(c.soeHeader)], c.soeHeader) { + return nil, buf, ErrWrongCodec(fingerprint) + } + value, newBuf, err := c.nativeFromBinary(newBuf) + if err != nil { + return nil, buf, err // if error, return original byte slice + } + return value, newBuf, nil +} + +// NativeFromTextual converts Avro data in JSON text format from the provided byte +// slice to Go native data types in accordance with the Avro schema supplied +// when creating the Codec. On success, it returns the decoded datum, along with +// a new byte slice with the decoded bytes consumed, and a nil error value. On +// error, it returns nil for the datum value, the original byte slice, and the +// error message. +// +// func ExampleNativeFromTextual() { +// codec, err := goavro.NewCodec(` +// { +// "type": "record", +// "name": "LongList", +// "fields" : [ +// {"name": "next", "type": ["null", "LongList"], "default": null} +// ] +// }`) +// if err != nil { +// fmt.Println(err) +// } +// +// // Convert native Go form to text Avro data +// text := []byte(`{"next":{"LongList":{"next":{"LongList":{"next":null}}}}}`) +// +// native, _, err := codec.NativeFromTextual(text) +// if err != nil { +// fmt.Println(err) +// } +// +// fmt.Printf("%v", native) +// // Output: map[next:map[LongList:map[next:map[LongList:map[next:]]]]] +// } +func (c *Codec) NativeFromTextual(buf []byte) (interface{}, []byte, error) { + value, newBuf, err := c.nativeFromTextual(buf) + if err != nil { + return nil, buf, err // if error, return original byte slice + } + return value, newBuf, nil +} + +// SingleFromNative appends the single-object-encoding byte slice representation +// of the provided native datum value to the provided byte slice in accordance +// with the Avro schema supplied when creating the Codec. It is supplied a byte +// slice to which to append the header and binary encoded data, along with the +// actual data to encode. On success, it returns a new byte slice with the +// encoded bytes appended, and a nil error value. On error, it returns the +// original byte slice, and the error message. +// +// func ExampleSingleItemEncoding() { +// codec, err := goavro.NewCodec(`"int"`) +// if err != nil { +// fmt.Fprintf(os.Stderr, "%s\n", err) +// return +// } +// +// buf, err := codec.SingleFromNative(nil, 3) +// if err != nil { +// fmt.Fprintf(os.Stderr, "%s\n", err) +// return +// } +// +// fmt.Println(buf) +// // Output: [195 1 143 92 57 63 26 213 117 114 6] +// } +func (c *Codec) SingleFromNative(buf []byte, datum interface{}) ([]byte, error) { + newBuf, err := c.binaryFromNative(append(buf, c.soeHeader...), datum) + if err != nil { + return buf, err + } + return newBuf, nil +} + +// TextualFromNative converts Go native data types to Avro data in JSON text format in +// accordance with the Avro schema supplied when creating the Codec. It is +// supplied a byte slice to which to append the encoded data and the actual data +// to encode. On success, it returns a new byte slice with the encoded bytes +// appended, and a nil error value. On error, it returns the original byte +// slice, and the error message. +// +// func ExampleTextualFromNative() { +// codec, err := goavro.NewCodec(` +// { +// "type": "record", +// "name": "LongList", +// "fields" : [ +// {"name": "next", "type": ["null", "LongList"], "default": null} +// ] +// }`) +// if err != nil { +// fmt.Println(err) +// } +// +// // Convert native Go form to text Avro data +// text, err := codec.TextualFromNative(nil, map[string]interface{}{ +// "next": map[string]interface{}{ +// "LongList": map[string]interface{}{ +// "next": map[string]interface{}{ +// "LongList": map[string]interface{}{ +// // NOTE: May omit fields when using default value +// }, +// }, +// }, +// }, +// }) +// if err != nil { +// fmt.Println(err) +// } +// +// fmt.Printf("%s", text) +// // Output: {"next":{"LongList":{"next":{"LongList":{"next":null}}}}} +// } +func (c *Codec) TextualFromNative(buf []byte, datum interface{}) ([]byte, error) { + newBuf, err := c.textualFromNative(buf, datum) + if err != nil { + return buf, err // if error, return original byte slice + } + return newBuf, nil +} + +// Schema returns the original schema used to create the Codec. +func (c *Codec) Schema() string { + return c.schemaOriginal +} + +// CanonicalSchema returns the Parsing Canonical Form of the schema according to +// the Avro specification. +func (c *Codec) CanonicalSchema() string { + return c.schemaCanonical +} + +// SchemaCRC64Avro returns a signed 64-bit integer Rabin fingerprint for the +// canonical schema. This method returns the signed 64-bit cast of the unsigned +// 64-bit schema Rabin fingerprint. +// +// DEPRECATED: This method has been replaced by the Rabin structure Codec field +// and is provided for backward compatibility only. +func (c *Codec) SchemaCRC64Avro() int64 { + return int64(c.Rabin) +} + +// convert a schema data structure to a codec, prefixing with specified +// namespace +func buildCodec(converters map[string]ConvertBuild, st map[string]*Codec, enclosingNamespace string, schema interface{}, cb *codecBuilder) (*Codec, error) { + switch schemaType := schema.(type) { + case map[string]interface{}: + return cb.mapBuilder(converters, st, enclosingNamespace, schemaType, cb) + case string: + return cb.stringBuilder(converters, st, enclosingNamespace, schemaType, nil, cb) + case []interface{}: + return cb.sliceBuilder(converters, st, enclosingNamespace, schemaType, cb) + default: + return nil, fmt.Errorf("unknown schema type: %T", schema) + } +} + +// Reach into the map, grabbing its "type". Use that to create the codec. +func buildCodecForTypeDescribedByMap(converters map[string]ConvertBuild, st map[string]*Codec, enclosingNamespace string, schemaMap map[string]interface{}, cb *codecBuilder) (codec *Codec, err error) { + t, ok := schemaMap["type"] + if !ok { + return nil, fmt.Errorf("missing type: %v", schemaMap) + } + + switch typeName := t.(type) { + case string: + // Already defined types may be abbreviated with its string name. + // EXAMPLE: "type":"array" + // EXAMPLE: "type":"enum" + // EXAMPLE: "type":"fixed" + // EXAMPLE: "type":"int" + // EXAMPLE: "type":"record" + // EXAMPLE: "type":"somePreviouslyDefinedCustomTypeString" + codec, err = cb.stringBuilder(converters, st, enclosingNamespace, typeName, schemaMap, cb) + case map[string]interface{}: + codec, err = cb.mapBuilder(converters, st, enclosingNamespace, typeName, cb) + case []interface{}: + return cb.sliceBuilder(converters, st, enclosingNamespace, typeName, cb) + default: + err = fmt.Errorf("type ought to be either string, map[string]interface{}, or []interface{}; received: %T", t) + } + if err == nil { + c, found := schemaMap["convert"] + if found { + cb, found := converters[c.(string)] + if !found { + codec = nil + err = fmt.Errorf("convert builder not found with name: %s", c) + } else { + if !codec.isNamedType { + codec = copyCodec(*codec) + } + binaryFromNative := codec.binaryFromNative + codec.binaryFromNative = cb(binaryFromNative) + } + } + } + return +} + +func copyCodec(codec Codec) *Codec { + return &codec +} + +func buildCodecForTypeDescribedByString(converters map[string]ConvertBuild, st map[string]*Codec, enclosingNamespace string, typeName string, schemaMap map[string]interface{}, cb *codecBuilder) (*Codec, error) { + isLogicalType := false + searchType := typeName + // logicalType will be non-nil for those fields without a logicalType property set + if lt := schemaMap["logicalType"]; lt != nil { + isLogicalType = true + searchType = fmt.Sprintf("%s.%s", typeName, lt) + } + + // NOTE: When codec already exists, return it. This includes both primitive and + // logicalType codecs added in NewCodec, and user-defined types, added while + // building the codec. + if cd, ok := st[searchType]; ok { + + // For "bytes.decimal" types verify that the scale and precision in this schema map match a cached codec before + // using the cached codec in favor of creating a new codec. + if searchType == "bytes.decimal" { + + // Search the cached codecs for a "bytes.decimal" codec with a "precision" and "scale" specified in the key, + // only if that matches return the cached codec. Otherwise, create a new codec for this "bytes.decimal". + decimalSearchType := fmt.Sprintf("bytes.decimal.%d.%d", int(schemaMap["precision"].(float64)), int(schemaMap["scale"].(float64))) + if cd2, ok := st[decimalSearchType]; ok { + return cd2, nil + } + + } else { + return cd, nil + } + } + + // Avro specification allows abbreviation of type name inside a namespace. + if enclosingNamespace != "" { + if cd, ok := st[enclosingNamespace+"."+typeName]; ok { + return cd, nil + } + } + + // There are only a small handful of complex Avro data types. + switch searchType { + case "array": + return makeArrayCodec(converters, st, enclosingNamespace, schemaMap, cb) + case "enum": + return makeEnumCodec(st, enclosingNamespace, schemaMap) + case "fixed": + return makeFixedCodec(st, enclosingNamespace, schemaMap) + case "map": + return makeMapCodec(converters, st, enclosingNamespace, schemaMap, cb) + case "record": + return makeRecordCodec(converters, st, enclosingNamespace, schemaMap, cb) + case "bytes.decimal": + return makeDecimalBytesCodec(st, enclosingNamespace, schemaMap) + case "fixed.decimal": + return makeDecimalFixedCodec(st, enclosingNamespace, schemaMap) + case "string.validated-string": + return makeValidatedStringCodec(st, enclosingNamespace, schemaMap) + default: + if isLogicalType { + delete(schemaMap, "logicalType") + return buildCodecForTypeDescribedByString(converters, st, enclosingNamespace, typeName, schemaMap, cb) + } + return nil, fmt.Errorf("unknown type name: %q", searchType) + } +} + +func registerNewNamedCodec(st map[string]*Codec, schemaMap map[string]interface{}, enclosingNamespace string) (c *Codec, err error) { + if c, err = registerNewCodec(st, schemaMap, enclosingNamespace); err == nil { + c.isNamedType = true + } + return +} + +// notion of enclosing namespace changes when record, enum, or fixed create a +// new namespace, for child objects. +func registerNewCodec(st map[string]*Codec, schemaMap map[string]interface{}, enclosingNamespace string) (*Codec, error) { + n, err := newNameFromSchemaMap(enclosingNamespace, schemaMap) + if err != nil { + return nil, err + } + c := &Codec{typeName: n} + st[n.fullName] = c + return c, nil +} + +// ErrWrongCodec is returned when an attempt is made to decode a single-object +// encoded value using the wrong codec. +type ErrWrongCodec uint64 + +func (e ErrWrongCodec) Error() string { return "wrong codec: " + strconv.FormatUint(uint64(e), 10) } + +// ErrNotSingleObjectEncoded is returned when an attempt is made to decode a +// single-object encoded value from a buffer that does not have the correct +// magic prefix. +type ErrNotSingleObjectEncoded string + +func (e ErrNotSingleObjectEncoded) Error() string { + return "cannot decode buffer as single-object encoding: " + string(e) +} diff --git a/fork/goavro/codec_test.go b/fork/goavro/codec_test.go new file mode 100644 index 0000000..404c371 --- /dev/null +++ b/fork/goavro/codec_test.go @@ -0,0 +1,333 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "bytes" + "fmt" + "os" + "testing" +) + +func ExampleCodecCanonicalSchema() { + schema := `{"type":"map","values":{"type":"enum","name":"foo","symbols":["alpha","bravo"]}}` + codec, err := NewCodec(schema) + if err != nil { + fmt.Println(err) + } else { + fmt.Println(codec.CanonicalSchema()) + } + // Output: {"type":"map","values":{"name":"foo","type":"enum","symbols":["alpha","bravo"]}} +} + +func TestCodecRabin(t *testing.T) { + cases := []struct { + Schema string + Rabin uint64 + }{ + { + Schema: `"null"`, + Rabin: 0x63dd24e7cc258f8a, + }, + { + Schema: `"boolean"`, + Rabin: 0x9f42fc78a4d4f764, + }, + { + Schema: `"int"`, + Rabin: 0x7275d51a3f395c8f, + }, + { + Schema: `"long"`, + Rabin: 0xd054e14493f41db7, + }, + { + Schema: `"float"`, + Rabin: 0x4d7c02cb3ea8d790, + }, + { + Schema: `"double"`, + Rabin: 0x8e7535c032ab957e, + }, + { + Schema: `"bytes"`, + Rabin: 0x4fc016dac3201965, + }, + { + Schema: `"string"`, + Rabin: 0x8f014872634503c7, + }, + { + Schema: `[ "int" ]`, + Rabin: 0xb763638a48b2fb03, + }, + { + Schema: `[ "int" , {"type":"boolean"} ]`, + Rabin: 0x4ad63578080c1602, + }, + { + Schema: `{"fields":[], "type":"record", "name":"foo"}`, + Rabin: 0xbd0c50c84319be7e, + }, + { + Schema: `{"fields":[], "type":"record", "name":"foo", "namespace":"x.y"}`, + Rabin: 0x521d1a6b830ec4ab, + }, + { + Schema: `{"fields":[], "type":"record", "name":"a.b.foo", "namespace":"x.y"}`, + Rabin: 0xbfefe5be5021e2b2, + }, + { + Schema: `{"fields":[], "type":"record", "name":"foo", "doc":"Useful info"}`, + Rabin: 0xbd0c50c84319be7e, + }, + { + Schema: `{"fields":[], "type":"record", "name":"foo", "aliases":["foo","bar"]}`, + Rabin: 0xbd0c50c84319be7e, + }, + { + Schema: `{"fields":[], "type":"record", "name":"foo", "doc":"foo", "aliases":["foo","bar"]}`, + Rabin: 0xbd0c50c84319be7e, + }, + { + Schema: `{"fields":[{"type":{"type":"boolean"}, "name":"f1"}], "type":"record", "name":"foo"}`, + Rabin: 0x6cd8eaf1c968a33b, + }, + { + Schema: `{ "fields":[{"type":"boolean", "aliases":[], "name":"f1", "default":true}, {"order":"descending","name":"f2","doc":"Hello","type":"int"}], "type":"record", "name":"foo"}`, + Rabin: 0xbc8d05bd57f4934a, + }, + { + Schema: `{"type":"enum", "name":"foo", "symbols":["A1"]}`, + Rabin: 0xa7fc039e15aa3169, + }, + { + Schema: `{"namespace":"x.y.z", "type":"enum", "name":"foo", "doc":"foo bar", "symbols":["A1", "A2"]}`, + Rabin: 0xc2433ae5f4999d8b, + }, + { + Schema: `{"name":"foo","type":"fixed","size":15}`, + Rabin: 0x18602ec3ed31a504, + }, + { + Schema: `{"namespace":"x.y.z", "type":"fixed", "name":"foo", "doc":"foo bar", "size":32}`, + Rabin: 0xd579d47693a6171e, + }, + { + Schema: `{ "items":{"type":"null"}, "type":"array"}`, + Rabin: 0xf7d13f2f68170a6d, + }, + { + Schema: `{ "values":"string", "type":"map"}`, + Rabin: 0x86ce965d92864572, + }, + { + Schema: `{"name":"PigValue","type":"record", "fields":[{"name":"value", "type":["null", "int", "long", "PigValue"]}]}`, + Rabin: 0xe795dc6656b7e95b, + }, + } + + for _, c := range cases { + codec, err := NewCodec(c.Schema) + if err != nil { + t.Fatalf("CASE: %s; cannot create code: %s", c.Schema, err) + } + if got, want := codec.Rabin, c.Rabin; got != want { + t.Errorf("CASE: %s; GOT: %#x; WANT: %#x", c.Schema, got, want) + } + } +} + +func TestSingleObjectEncoding(t *testing.T) { + t.Run("int", func(*testing.T) { + schema := `"int"` + + codec, err := NewCodec(schema) + if err != nil { + t.Fatalf("cannot create code: %s", err) + } + + t.Run("encoding", func(t *testing.T) { + t.Run("does not modify source buf when cannot encode", func(t *testing.T) { + buf := []byte{0xDE, 0xAD, 0xBE, 0xEF} + + buf, err = codec.SingleFromNative(buf, "strings cannot be encoded as int") + ensureError(t, err, "cannot encode binary int") + + if got, want := buf, []byte("\xDE\xAD\xBE\xEF"); !bytes.Equal(got, want) { + t.Errorf("GOT: %v; WANT: %v", got, want) + } + }) + + t.Run("appends header then encoded data", func(t *testing.T) { + const original = "\x01\x02\x03\x04" + buf := []byte(original) + + buf, err = codec.SingleFromNative(buf, 3) + ensureError(t, err) + + fp := "\xC3\x01" + "\x8F\x5C\x39\x3F\x1A\xD5\x75\x72" + + if got, want := buf, []byte(original+fp+"\x06"); !bytes.Equal(got, want) { + t.Errorf("\nGOT:\n\t%v;\nWANT:\n\t%v", got, want) + } + }) + }) + + t.Run("decoding", func(t *testing.T) { + const original = "" + buf := []byte(original) + + buf, err = codec.SingleFromNative(nil, 3) + ensureError(t, err) + + buf = append(buf, "\xDE\xAD"...) // append some junk + + datum, newBuf, err := codec.NativeFromSingle(buf) + ensureError(t, err) + + if got, want := datum, int32(3); got != want { + t.Errorf("GOT: %v; WANT: %v", got, want) + } + + // ensure junk is not disturbed + if got, want := newBuf, []byte("\xDE\xAD"); !bytes.Equal(got, want) { + t.Errorf("\nGOT:\n\t%q;\nWANT:\n\t%q", got, want) + } + }) + }) + + t.Run("record round trip", func(t *testing.T) { + codec, err := NewCodec(` +{ + "type": "record", + "name": "LongList", + "fields" : [ + {"name": "next", "type": ["null", "LongList"], "default": null} + ] +} +`) + ensureError(t, err) + + // NOTE: May omit fields when using default value + initial := `{"next":{"LongList":{}}}` + + // NOTE: Textual encoding will show all fields, even those with values that + // match their default values + final := `{"next":{"LongList":{"next":null}}}` + + // Convert textual Avro data (in Avro JSON format) to native Go form + datum, _, err := codec.NativeFromTextual([]byte(initial)) + ensureError(t, err) + + // Convert native Go form to single-object encoding form + buf, err := codec.SingleFromNative(nil, datum) + ensureError(t, err) + + // Convert single-object encoding form back to native Go form + datum, _, err = codec.NativeFromSingle(buf) + ensureError(t, err) + + // Convert native Go form to textual Avro data + buf, err = codec.TextualFromNative(nil, datum) + ensureError(t, err) + + if got, want := string(buf), final; got != want { + t.Fatalf("GOT: %v; WANT: %v", got, want) + } + }) +} + +func ExampleSingleItemEncoding() { + codec, err := NewCodec(`"int"`) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return + } + + buf, err := codec.SingleFromNative(nil, 3) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return + } + + fmt.Println(buf) + // Output: [195 1 143 92 57 63 26 213 117 114 6] +} + +func ExampleSingleItemDecoding() { + codec1, err := NewCodec(`"int"`) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return + } + + // Create a map of fingerprint values to corresponding Codec instances. + codex := make(map[uint64]*Codec) + codex[codec1.Rabin] = codec1 + + // Later on when you want to decode such a slice of bytes as a Single-Object + // Encoding, obtain the Rabin fingerprint of the schema used to encode the + // data. + buf := []byte{195, 1, 143, 92, 57, 63, 26, 213, 117, 114, 6} + + fingerprint, newBuf, err := FingerprintFromSOE(buf) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return + } + + // Get a previously stored Codec from the codex map. + codec2, ok := codex[fingerprint] + if !ok { + fmt.Fprintf(os.Stderr, "unknown codec: %d\n", fingerprint) + return + } + + // Use the fetched Codec to decode the buffer as a SOE. + datum, _, err := codec2.NativeFromBinary(newBuf) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return + } + fmt.Println(datum) + // Output: 3 +} + +func Test_buildCodecForTypeDescribedByString_CacheRespectsPrecisionScale(t *testing.T) { + schemaMap := map[string]interface{}{ + "type": "bytes", + "logicalType": "decimal", + "precision": float64(4), + "scale": float64(2), + } + cachedCodecIdentifier := "preexisting-cached-coded" + cache := map[string]*Codec{ + "bytes.decimal": nil, // precision.scale-agnostic codec + "bytes.decimal.4.2": { + schemaOriginal: cachedCodecIdentifier, // using field as identifier + }, + } + + // cached bytes.decimal codec with matching precision.scale is returned + cacheHit, err := buildCodecForTypeDescribedByString(nil, cache, "", "bytes", schemaMap, nil) + ensureError(t, err) // ensure NO error + if cacheHit.schemaOriginal != cachedCodecIdentifier { + t.Errorf("GOT: %v; WANT: %v", cacheHit.schemaOriginal, cachedCodecIdentifier) + } + + // cached codec with unmatching precision.scale is not returned + schemaMap["scale"] = float64(1) + cacheMiss, err := buildCodecForTypeDescribedByString(nil, cache, "", "bytes", schemaMap, nil) + ensureError(t, err) // ensure NO error + if cacheMiss.schemaOriginal == cachedCodecIdentifier { + t.Errorf("GOT: %v; WANT: %v", cacheMiss.schemaOriginal, "!= "+cachedCodecIdentifier) + } +} diff --git a/fork/goavro/debug_development.go b/fork/goavro/debug_development.go new file mode 100644 index 0000000..eff913f --- /dev/null +++ b/fork/goavro/debug_development.go @@ -0,0 +1,13 @@ +// +build goavro_debug + +package goavro + +import ( + "fmt" + "os" +) + +// debug formats and prints arguments to stderr for development builds +func debug(f string, a ...interface{}) { + os.Stderr.Write([]byte("goavro: " + fmt.Sprintf(f, a...))) +} diff --git a/fork/goavro/debug_release.go b/fork/goavro/debug_release.go new file mode 100644 index 0000000..18d5c94 --- /dev/null +++ b/fork/goavro/debug_release.go @@ -0,0 +1,7 @@ +// +build !goavro_debug + +package goavro + +// debug is a no-op for release builds, and the function call is optimized out +// by the compiler. +func debug(_ string, _ ...interface{}) {} diff --git a/fork/goavro/doc.go b/fork/goavro/doc.go new file mode 100644 index 0000000..1358af6 --- /dev/null +++ b/fork/goavro/doc.go @@ -0,0 +1,68 @@ +/* +Package goavro is a library that encodes and decodes Avro data. + +Goavro provides methods to encode native Go data into both binary and textual +JSON Avro data, and methods to decode both binary and textual JSON Avro data to +native Go data. + +Goavro also provides methods to read and write Object Container File (OCF) +formatted files, and the library contains example programs to read and write OCF +files. + +Usage Example: + + package main + + import ( + "fmt" + + "github.com/linkedin/goavro" + ) + + func main() { + codec, err := goavro.NewCodec(` + { + "type": "record", + "name": "LongList", + "fields" : [ + {"name": "next", "type": ["null", "LongList", {"type": "long", "logicalType": "timestamp-millis"}], "default": null} + ] + }`) + if err != nil { + fmt.Println(err) + } + + // NOTE: May omit fields when using default value + textual := []byte(`{"next":{"LongList":{}}}`) + + // Convert textual Avro data (in Avro JSON format) to native Go form + native, _, err := codec.NativeFromTextual(textual) + if err != nil { + fmt.Println(err) + } + + // Convert native Go form to binary Avro data + binary, err := codec.BinaryFromNative(nil, native) + if err != nil { + fmt.Println(err) + } + + // Convert binary Avro data back to native Go form + native, _, err = codec.NativeFromBinary(binary) + if err != nil { + fmt.Println(err) + } + + // Convert native Go form to textual Avro data + textual, err = codec.TextualFromNative(nil, native) + if err != nil { + fmt.Println(err) + } + + // NOTE: Textual encoding will show all fields, even those with values that + // match their default values + fmt.Println(string(textual)) + // Output: {"next":{"LongList":{"next":null}}} + } +*/ +package goavro diff --git a/fork/goavro/ensure_test.go b/fork/goavro/ensure_test.go new file mode 100644 index 0000000..3160ae7 --- /dev/null +++ b/fork/goavro/ensure_test.go @@ -0,0 +1,85 @@ +package goavro + +// NOTE: This file was copied from https://github.com/karrick/gorill + +import ( + "fmt" + "strings" + "testing" +) + +func ensureBuffer(tb testing.TB, buf []byte, n int, want string) { + tb.Helper() + if got, want := n, len(want); got != want { + tb.Fatalf("GOT: %v; WANT: %v", got, want) + } + if got, want := string(buf[:n]), want; got != want { + tb.Errorf("GOT: %v; WANT: %v", got, want) + } +} + +func ensureError(tb testing.TB, err error, contains ...string) { + tb.Helper() + if len(contains) == 0 || (len(contains) == 1 && contains[0] == "") { + if err != nil { + tb.Fatalf("GOT: %v; WANT: %v", err, contains) + } + } else if err == nil { + tb.Errorf("GOT: %v; WANT: %v", err, contains) + } else { + for _, stub := range contains { + if stub != "" && !strings.Contains(err.Error(), stub) { + tb.Errorf("GOT: %v; WANT: %q", err, stub) + } + } + } +} + +func ensurePanic(tb testing.TB, want string, callback func()) { + tb.Helper() + defer func() { + r := recover() + if r == nil { + tb.Fatalf("GOT: %v; WANT: %v", r, want) + return + } + if got := fmt.Sprintf("%v", r); got != want { + tb.Fatalf("GOT: %v; WANT: %v", got, want) + } + }() + callback() +} + +// ensureNoPanic prettifies the output so one knows which test case caused a +// panic. +func ensureNoPanic(tb testing.TB, label string, callback func()) { + tb.Helper() + defer func() { + if r := recover(); r != nil { + tb.Fatalf("TEST: %s: GOT: %v", label, r) + } + }() + callback() +} + +func ensureStringSlicesMatch(tb testing.TB, actual, expected []string) { + tb.Helper() + if got, want := len(actual), len(expected); got != want { + tb.Errorf("GOT: %v; WANT: %v", got, want) + } + la := len(actual) + le := len(expected) + for i := 0; i < la || i < le; i++ { + if i < la { + if i < le { + if got, want := actual[i], expected[i]; got != want { + tb.Errorf("GOT: %q; WANT: %q", got, want) + } + } else { + tb.Errorf("GOT: %q (extra)", actual[i]) + } + } else if i < le { + tb.Errorf("WANT: %q (missing)", expected[i]) + } + } +} diff --git a/fork/goavro/enum.go b/fork/goavro/enum.go new file mode 100644 index 0000000..73e785b --- /dev/null +++ b/fork/goavro/enum.go @@ -0,0 +1,105 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "fmt" + "io" +) + +// enum does not have child objects, therefore whatever namespace it defines is +// just to store its name in the symbol table. +func makeEnumCodec(st map[string]*Codec, enclosingNamespace string, schemaMap map[string]interface{}) (*Codec, error) { + c, err := registerNewNamedCodec(st, schemaMap, enclosingNamespace) + if err != nil { + return nil, fmt.Errorf("enum ought to have valid name: %s", err) + } + + // enum type must have symbols + s1, ok := schemaMap["symbols"] + if !ok { + return nil, fmt.Errorf("enum %q ought to have symbols key", c.typeName) + } + s2, ok := s1.([]interface{}) + if !ok || len(s2) == 0 { + return nil, fmt.Errorf("enum %q symbols ought to be non-empty array of strings: %v", c.typeName, s1) + } + symbols := make([]string, len(s2)) + for i, s := range s2 { + symbol, ok := s.(string) + if !ok { + return nil, fmt.Errorf("enum %q symbol %d ought to be non-empty string; received: %T", c.typeName, i+1, symbol) + } + if err := checkString(symbol); err != nil { + return nil, fmt.Errorf("enum %q symbol %d ought to %s", c.typeName, i+1, err) + } + symbols[i] = symbol + } + + c.nativeFromBinary = func(buf []byte) (interface{}, []byte, error) { + var value interface{} + var err error + var index int64 + + if value, buf, err = longNativeFromBinary(buf); err != nil { + return nil, nil, fmt.Errorf("cannot decode binary enum %q index: %s", c.typeName, err) + } + index = value.(int64) + if index < 0 || index >= int64(len(symbols)) { + return nil, nil, fmt.Errorf("cannot decode binary enum %q: index ought to be between 0 and %d; read index: %d", c.typeName, len(symbols)-1, index) + } + return symbols[index], buf, nil + } + c.binaryFromNative = func(buf []byte, datum interface{}) ([]byte, error) { + someString, ok := datum.(string) + if !ok { + return nil, fmt.Errorf("cannot encode binary enum %q: expected string; received: %T", c.typeName, datum) + } + for i, symbol := range symbols { + if symbol == someString { + return longBinaryFromNative(buf, i) + } + } + return nil, fmt.Errorf("cannot encode binary enum %q: value ought to be member of symbols: %v; %q", c.typeName, symbols, someString) + } + c.nativeFromTextual = func(buf []byte) (interface{}, []byte, error) { + if buf, _ = advanceToNonWhitespace(buf); len(buf) == 0 { + return nil, nil, fmt.Errorf("cannot decode textual enum: %s", io.ErrShortBuffer) + } + // decode enum string + var value interface{} + var err error + value, buf, err = stringNativeFromTextual(buf) + if err != nil { + return nil, nil, fmt.Errorf("cannot decode textual enum: expected key: %s", err) + } + someString := value.(string) + for _, symbol := range symbols { + if symbol == someString { + return someString, buf, nil + } + } + return nil, nil, fmt.Errorf("cannot decode textual enum %q: value ought to be member of symbols: %v; %q", c.typeName, symbols, someString) + } + c.textualFromNative = func(buf []byte, datum interface{}) ([]byte, error) { + someString, ok := datum.(string) + if !ok { + return nil, fmt.Errorf("cannot encode textual enum %q: expected string; received: %T", c.typeName, datum) + } + for _, symbol := range symbols { + if symbol == someString { + return stringTextualFromNative(buf, someString) + } + } + return nil, fmt.Errorf("cannot encode textual enum %q: value ought to be member of symbols: %v; %q", c.typeName, symbols, someString) + } + + return c, nil +} diff --git a/fork/goavro/enum_test.go b/fork/goavro/enum_test.go new file mode 100644 index 0000000..1b0ede9 --- /dev/null +++ b/fork/goavro/enum_test.go @@ -0,0 +1,136 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "encoding/json" + "fmt" + "testing" +) + +func TestSchemaEnum(t *testing.T) { + testSchemaValid(t, `{"type":"enum","name":"foo","symbols":["alpha","bravo"]}`) +} + +func TestEnumName(t *testing.T) { + testSchemaInvalid(t, `{"type":"enum","symbols":["alpha","bravo"]}`, "Enum ought to have valid name: schema ought to have name key") + testSchemaInvalid(t, `{"type":"enum","name":3}`, "Enum ought to have valid name: schema name ought to be non-empty string") + testSchemaInvalid(t, `{"type":"enum","name":""}`, "Enum ought to have valid name: schema name ought to be non-empty string") + testSchemaInvalid(t, `{"type":"enum","name":"&foo","symbols":["alpha","bravo"]}`, "Enum ought to have valid name: schema name ought to start with") + testSchemaInvalid(t, `{"type":"enum","name":"foo&","symbols":["alpha","bravo"]}`, "Enum ought to have valid name: schema name ought to have second and remaining") +} + +func TestEnumSymbols(t *testing.T) { + testSchemaInvalid(t, `{"type":"enum","name":"e1"}`, `Enum "e1" ought to have symbols key`) + testSchemaInvalid(t, `{"type":"enum","name":"e1","symbols":3}`, `Enum "e1" symbols ought to be non-empty array of strings`) + testSchemaInvalid(t, `{"type":"enum","name":"e1","symbols":[]}`, `Enum "e1" symbols ought to be non-empty array of strings`) +} + +func TestEnumSymbolInvalid(t *testing.T) { + testSchemaInvalid(t, `{"type":"enum","name":"e1","symbols":[3]}`, `Enum "e1" symbol 1 ought to be non-empty string`) + testSchemaInvalid(t, `{"type":"enum","name":"e1","symbols":[""]}`, `Enum "e1" symbol 1 ought to be non-empty string`) + testSchemaInvalid(t, `{"type":"enum","name":"e1","symbols":["string-with-invalid-characters"]}`, `Enum "e1" symbol 1 ought to have second and remaining`) +} + +func TestEnumDecodeError(t *testing.T) { + testBinaryDecodeFail(t, `{"type":"enum","name":"e1","symbols":["alpha","bravo"]}`, nil, "short buffer") + testBinaryDecodeFail(t, `{"type":"enum","name":"e1","symbols":["alpha","bravo"]}`, []byte("\x01"), `cannot decode binary enum "e1": index ought to be between 0 and 1`) + testBinaryDecodeFail(t, `{"type":"enum","name":"e1","symbols":["alpha","bravo"]}`, []byte("\x04"), `cannot decode binary enum "e1": index ought to be between 0 and 1`) +} + +func TestEnumEncodeError(t *testing.T) { + testBinaryEncodeFail(t, `{"type":"enum","name":"e1","symbols":["alpha","bravo"]}`, 13, `cannot encode binary enum "e1": expected string; received: int`) + testBinaryEncodeFail(t, `{"type":"enum","name":"e1","symbols":["alpha","bravo"]}`, "charlie", `cannot encode binary enum "e1": value ought to be member of symbols`) +} + +func TestEnumEncode(t *testing.T) { + testBinaryCodecPass(t, `{"type":"enum","name":"e1","symbols":["alpha","bravo"]}`, "alpha", []byte("\x00")) + testBinaryCodecPass(t, `{"type":"enum","name":"e1","symbols":["alpha","bravo"]}`, "bravo", []byte("\x02")) +} + +func TestEnumTextCodec(t *testing.T) { + testTextCodecPass(t, `{"type":"enum","name":"e1","symbols":["alpha","bravo"]}`, "alpha", []byte(`"alpha"`)) + testTextCodecPass(t, `{"type":"enum","name":"e1","symbols":["alpha","bravo"]}`, "bravo", []byte(`"bravo"`)) + testTextEncodeFail(t, `{"type":"enum","name":"e1","symbols":["alpha","bravo"]}`, "charlie", `cannot encode textual enum "e1": value ought to be member of symbols`) + testTextDecodeFail(t, `{"type":"enum","name":"e1","symbols":["alpha","bravo"]}`, []byte(`"charlie"`), `cannot decode textual enum "e1": value ought to be member of symbols`) +} + +func TestGH233(t *testing.T) { + // here's the fail case + // testTextCodecPass(t, `{"type":"record","name":"FooBar","namespace":"com.foo.bar","fields":[{"name":"event","type":["null",{"type":"enum","name":"FooBarEvent","symbols":["CREATED","UPDATED"]}]}]}`, map[string]interface{}{"event": Union("FooBarEvent", "CREATED")}, []byte(`{"event":{"FooBarEvent":"CREATED"}}`)) + // remove the namespace and it passes + testTextCodecPass(t, `{"type":"record","name":"FooBar","fields":[{"name":"event","type":["null",{"type":"enum","name":"FooBarEvent","symbols":["CREATED","UPDATED"]}]}]}`, map[string]interface{}{"event": Union("FooBarEvent", "CREATED")}, []byte(`{"event":{"FooBarEvent":"CREATED"}}`)) + // experiments + // the basic enum + testTextCodecPass(t, `{"type":"enum","name":"FooBarEvent","symbols":["CREATED","UPDATED"]}`, "CREATED", []byte(`"CREATED"`)) + // the basic enum with namespace + testTextCodecPass(t, `{"type":"enum","name":"FooBarEvent","namespace":"com.foo.bar","symbols":["CREATED","UPDATED"]}`, "CREATED", []byte(`"CREATED"`)) + // union with enum + testTextCodecPass(t, `["null",{"type":"enum","name":"FooBarEvent","symbols":["CREATED","UPDATED"]}]`, Union("FooBarEvent", "CREATED"), []byte(`{"FooBarEvent":"CREATED"}`)) + // FAIL: union with enum with namespace: cannot determine codec: "FooBarEvent" + // testTextCodecPass(t, `["null",{"type":"enum","name":"FooBarEvent","namespace":"com.foo.bar","symbols":["CREATED","UPDATED"]}]`, Union("FooBarEvent", "CREATED"), []byte(`{"FooBarEvent":"CREATED"}`)) + // conclusion, union is not handling namespaces correctly + // try union with record instead of enum (records and enums both have namespaces) + // get a basic record going + testTextCodecPass(t, `{"type":"record","name":"LongList","fields":[{"name":"next","type":["null","LongList"],"default":null}]}`, map[string]interface{}{"next": Union("LongList", map[string]interface{}{"next": nil})}, []byte(`{"next":{"LongList":{"next":null}}}`)) + // add a namespace to the record + // fails in the same way cannot determine codec: "LongList" for key: "next" + // testTextCodecPass(t, `{"type":"record","name":"LongList","namespace":"com.foo.bar","fields":[{"name":"next","type":["null","LongList"],"default":null}]}`, map[string]interface{}{"next": Union("LongList", map[string]interface{}{"next": nil})}, []byte(`{"next":{"LongList":{"next":null}}}`)) + // + // experiments on syntax solutions + // testTextCodecPass(t, `["null",{"type":"enum","name":"com.foo.bar.FooBarEvent","symbols":["CREATED","UPDATED"]}]`, Union("com.foo.bar.FooBarEvent", "CREATED"), []byte(`{"FooBarEvent":"CREATED"}`)) + // thie TestUnionMapRecordFitsInRecord tests binary from Native, but not native from textual + // that's where the error is happening + // if the namespace is specified in the incoming name it works + testTextCodecPass(t, `{"type":"record","name":"ns1.LongList","fields":[{"name":"next","type":["null","LongList"],"default":null}]}`, map[string]interface{}{"next": Union("ns1.LongList", map[string]interface{}{"next": nil})}, []byte(`{"next":{"ns1.LongList":{"next":null}}}`)) + + // try the failcase with the namespace specified on the input + testTextCodecPass(t, `{"type":"record","name":"FooBar","namespace":"com.foo.bar","fields":[{"name":"event","type":["null",{"type":"enum","name":"FooBarEvent","symbols":["CREATED","UPDATED"]}]}]}`, map[string]interface{}{"event": Union("com.foo.bar.FooBarEvent", "CREATED")}, []byte(`{"event":{"com.foo.bar.FooBarEvent":"CREATED"}}`)) + +} + +func ExampleCheckSolutionGH233() { + const avroSchema = ` + { + "type": "record", + "name": "FooBar", + "namespace": "com.foo.bar", + "fields": [ + { + "name": "event", + "type": [ + "null", + { + "type": "enum", + "name": "FooBarEvent", + "symbols": ["CREATED", "UPDATED"] + } + ] + } + ] + } + ` + codec, _ := NewCodec(avroSchema) + + const avroJson = `{"event":{"com.foo.bar.FooBarEvent":"CREATED"}}` + + native, _, err := codec.NativeFromTextual([]byte(avroJson)) + if err != nil { + panic(err) + } + + blob, err := json.Marshal(native) + if err != nil { + panic(err) + } + fmt.Println(string(blob)) + // Output: {"event":{"com.foo.bar.FooBarEvent":"CREATED"}} + +} diff --git a/fork/goavro/eos.go b/fork/goavro/eos.go new file mode 100644 index 0000000..1582578 --- /dev/null +++ b/fork/goavro/eos.go @@ -0,0 +1,32 @@ +package goavro + +var eosToAvro map[string]string + +func init() { + eosToAvro = map[string]string{ + "bool": "boolean", + "int8": "int", + "uint8": "int", + "int16": "int", + "uint16": "int", + "int32": "int", + "uint32": "long", + "int64": "long", + "uint64": "long", + "varint32": "int", + "varuint32": "long", + "float32": "float", + "float64": "double", + "time_point": "long", + "time_point_sec": "long", + "block_timestamp_type": "long", + "name": "string", + "bytes": "bytes", + "string": "string", + "checksum160": "bytes", + "checksum256": "bytes", + "checksum512": "bytes", + "symbol": "string", + "symbol_code": "string", + } +} diff --git a/fork/goavro/examples/165/main.go b/fork/goavro/examples/165/main.go new file mode 100644 index 0000000..687740c --- /dev/null +++ b/fork/goavro/examples/165/main.go @@ -0,0 +1,105 @@ +// #165 +// +// This exemplifies three ways to encode an Avro record. Note that I did not +// say "Go struct" because there is no struct in this example. `goavro` expects +// data that is to be encoded as an Avro record to be given in the form of a +// `map[string]interface{}`, so create the map, populate whichever key-value +// pairs that the Avro record type requires, and pass it on to one of the +// encoding methods. +// +// Note that there are three ways to encode Avro data into binary. The first +// way is to use the BinaryFromNative method, which simply encodes the provided +// value as a sequence of bytes, appending the new bytes to the provided byte +// slice, and returning the new byte slice. This binary data is completely +// unusable by any process that wants to decode the bytes unless the original +// schema that was used to encode the data is known when trying to decode the +// bytes. +// +// The second example is using Avro's Single-Object Encoding specification, +// where a magic byte sequence, then the schema's fingerprint is first appended +// to the provided byte slice, then finally the binary encoded bytes of the data +// is appended. This method is useful for processes where the decoding reader +// will pull off a chunk of bytes, use the fingerprint to look up the schema in +// some sort of schema registry, then use that schema to decode the bytes that +// follow. This method is used by Kafka producers and consumers, where rather +// than shoving the schema text on the wire for each method is wasteful compared +// to shoving a tiny schema fingerprint on the wire. This method only uses 10 +// more bytes to uniquely identify the schema. +// +// Finally the third example uses the Avro Object Container File format to +// encode the data, where the OCF file has a copy of the schema used to encode +// the file. Because the original schema prefixes the entire file, any Avro +// reader can decode the contents of the entire file without having to look up +// its schema in a registry. + +package main + +import ( + "os" + + "github.com/linkedin/goavro/v2" +) + +const loginEventAvroSchema = `{"type": "record", "name": "LoginEvent", "fields": [{"name": "Username", "type": "string"}]}` + +func main() { + codec, err := goavro.NewCodec(loginEventAvroSchema) + if err != nil { + panic(err) + } + + m := map[string]interface{}{ + "Username": "superman", + } + + // Let's dip our feet into just encoding a single item into binary format. + // There is not much to do with the output from binary if you intend on + // creating an OCF file, because OCF will do this encoding for us. The + // result is an unadorned stream of binary bytes that can never be decoded + // unless you happen to know the schema that was used to encode it. + binary, err := codec.BinaryFromNative(nil, m) + if err != nil { + panic(err) + } + _ = binary + + // Next, let's try encoding the same item using Single-Object Encoding, + // another format that is useful when sending a bunch of objects into a + // Kafka stream. Note this method prefixes the binary bytes with a schema + // fingerprint, used by the reader on the stream to lookup the contents of + // the schema used to encode the value. Again, unless the reader can fetch + // the schema contents from a schema source-of-truth, this binary sequence + // will never be decodable. + single, err := codec.SingleFromNative(nil, m) + if err != nil { + panic(err) + } + _ = single + + // Next, let's make an OCF file from the values. The OCF format prefixes + // the entire file with the required schema that was used to encode the + // data, so it is readable from any Avro decoder that can read OCF files. + // No other source of information is needed to decode the file created by + // this process, unlike the above two examples. Also note that we do not + // send OCF the encoded blobs to write, but just append the values and it + // will encode each of the values for us. + var values []map[string]interface{} + values = append(values, m) + values = append(values, map[string]interface{}{"Username": "batman"}) + values = append(values, map[string]interface{}{"Username": "wonder woman"}) + + f, err := os.Create("event.avro") + if err != nil { + panic(err) + } + ocfw, err := goavro.NewOCFWriter(goavro.OCFConfig{ + W: f, + Codec: codec, + }) + if err != nil { + panic(err) + } + if err = ocfw.Append(values); err != nil { + panic(err) + } +} diff --git a/fork/goavro/examples/ab2t/main.go b/fork/goavro/examples/ab2t/main.go new file mode 100644 index 0000000..8e2e855 --- /dev/null +++ b/fork/goavro/examples/ab2t/main.go @@ -0,0 +1,105 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "io" + "os" + "path/filepath" + "sync" + + "github.com/linkedin/goavro/v2" +) + +func usage() { + executable, err := os.Executable() + if err != nil { + executable = os.Args[0] + } + base := filepath.Base(executable) + fmt.Fprintf(os.Stderr, "Usage of %s:\n", base) + fmt.Fprintf(os.Stderr, "\t%s [file1.avro [file2.avro [file3.avro]]]\n", base) + fmt.Fprintf(os.Stderr, "\tWhen filename is hyphen, %s will read from its standard input.\n", base) + flag.PrintDefaults() + os.Exit(2) +} + +func main() { + args := os.Args[1:] + if len(args) == 0 { + usage() + } + for _, arg := range args { + if arg == "-" { + stat, err := os.Stdin.Stat() + if err != nil { + bail(err) + } + if (stat.Mode() & os.ModeCharDevice) != 0 { + usage() + } + if err = dumpFromReader(os.Stdin); err != nil { + bail(err) + } + if err = os.Stdin.Close(); err != nil { + bail(err) + } + continue + } + fh, err := os.Open(arg) + if err != nil { + bail(err) + } + if err := dumpFromReader(bufio.NewReader(fh)); err != nil { + bail(err) + } + if err := fh.Close(); err != nil { + bail(err) + } + } +} + +func dumpFromReader(ior io.Reader) error { + ocf, err := goavro.NewOCFReader(ior) + if err != nil { + return err + } + + codec := ocf.Codec() + data := make(chan interface{}, 100) + finishedOutput := new(sync.WaitGroup) + finishedOutput.Add(1) + + go textualFromNative(codec, data, finishedOutput) + + for ocf.Scan() { + datum, err := ocf.Read() + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + continue + } + data <- datum + } + close(data) + finishedOutput.Wait() + + return ocf.Err() +} + +func textualFromNative(codec *goavro.Codec, data <-chan interface{}, finishedOutput *sync.WaitGroup) { + for datum := range data { + buf, err := codec.TextualFromNative(nil, datum) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + continue + } + fmt.Println(string(buf)) + } + finishedOutput.Done() +} + +func bail(err error) { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(1) +} diff --git a/fork/goavro/examples/arw/README.md b/fork/goavro/examples/arw/README.md new file mode 100644 index 0000000..78054f4 --- /dev/null +++ b/fork/goavro/examples/arw/README.md @@ -0,0 +1,49 @@ +# arw + +Avro ReWrite + +Provide command line utility to rewrite an Avro Object Container File +(OCF), while changing the block count, the compression algorithm, or +upgrading the schema. Note that when upgrading the schema, the new +schema must be able to properly encode the data read using the old +schema. + +Why would a person want to upgrade the schema for an existing OCF? +Perhaps if one wants to append data to it using the new schema. + +Example use: + +``` +arw -summary -bc 100 -compression deflate -schema new-schema.avsc source.avro destination.avro +``` + +If summary option, `-summary`, is provided, `arw` will provide summary +information while rewriting the OCF. + +If verbose option, `-v`, is provided, `arw` will provide verbose +information while rewriting the OCF. Specifying verbose implies the +summary option. + +If block count option, `-bc`, is provided, then each block will have +no more items than specified. If omitted, then `arw` will re-encode +blocks of the same length as found in `source.avro`. For instance, if +the first block had 10 items, and the second has 15, then the +`destination.avro` file will also have 10 items in the first block and +15 items in the second block. + +If compression option is omitted, then `arw` will use the same +compression algorithm as found in `source.avro`. + +If schema option is omitted, then `arw` will write the new Avro file +using the same schema as found in `source.avro`. If provided, `arw` +will read the source Avro file using its provided schema, but attempt +to encode and write the destination Avro file using the newly provided +schema. If an item fails to encode using the new schema, the process +will be aborted and an error message will be provided. + +If `source.avro` is a hyphen character, `-`, then `arw` will read from +standard input. If `destination.avro` is a hyphen character, then +`arw` will write to standard output. + +Invoking `arw` without any of the options simply copies the OCF file, +verifying the contents of the data along the way. diff --git a/fork/goavro/examples/arw/main.go b/fork/goavro/examples/arw/main.go new file mode 100644 index 0000000..c24a6de --- /dev/null +++ b/fork/goavro/examples/arw/main.go @@ -0,0 +1,236 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/linkedin/goavro/v2" +) + +func bail(err error) { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(1) +} + +func usage(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + } + executable, err := os.Executable() + if err != nil { + executable = os.Args[0] + } + base := filepath.Base(executable) + fmt.Fprintf(os.Stderr, "Usage of %s:\n", base) + fmt.Fprintf(os.Stderr, "\t%s [-v] [-summary] [-bc N] [-compression null|deflate|snappy] [-schema new-schema.avsc] source.avro destination.avro\n", base) + fmt.Fprintf(os.Stderr, "\tWhen source.avro pathname is hyphen, %s will read from its standard input.\n", base) + fmt.Fprintf(os.Stderr, "\tWhen destination.avro pathname is hyphen, %s will write to its standard output.\n", base) + flag.PrintDefaults() + os.Exit(2) +} + +var ( + blockCount *int + compressionName, schemaPathname *string + summary, verbose *bool +) + +func init() { + compressionName = flag.String("compression", "", "compression codec ('null', 'deflate', 'snappy'; default: use source compression)") + blockCount = flag.Int("bc", 0, "max count of items in each block (default: use source block boundaries)") + schemaPathname = flag.String("schema", "", "pathname to new schema (default: use source schema)") + summary = flag.Bool("summary", false, "print summary information to stderr") + verbose = flag.Bool("v", false, "print verbose information to stderr (implies: -summary)") +} + +func main() { + flag.Parse() + + if count := len(flag.Args()); count != 2 { + usage(fmt.Errorf("wrong number of arguments: %d", count)) + } + + if *blockCount < 0 { + usage(fmt.Errorf("count must be greater or equal to 0: %d", *blockCount)) + } + + if *verbose { + *summary = true + } + + var err error + var fromF io.ReadCloser + var toF io.WriteCloser + + if srcPathname := flag.Arg(0); srcPathname == "-" { + stat, err := os.Stdin.Stat() + if err != nil { + bail(err) + } + if (stat.Mode() & os.ModeCharDevice) != 0 { + usage(errors.New("cannot read from standard input when connected to terminal")) + } + fromF = os.Stdin + if *summary { + fmt.Fprintf(os.Stderr, "reading from stdin\n") + } + } else { + fromF, err = os.Open(srcPathname) + if err != nil { + bail(err) + } + defer func(ioc io.Closer) { + if err := ioc.Close(); err != nil { + bail(err) + } + }(fromF) + if *summary { + fmt.Fprintf(os.Stderr, "reading from %s\n", flag.Arg(0)) + } + } + + if destPathname := flag.Arg(1); destPathname == "-" { + stat, err := os.Stdout.Stat() + if err != nil { + bail(err) + } + // if *verbose { // DEBUG + // fmt.Fprintf(os.Stderr, "standard output mode: %v\n", stat.Mode()) + // } + if (stat.Mode() & os.ModeCharDevice) != 0 { + usage(errors.New("cannot send to standard output when connected to terminal")) + } + toF = os.Stdout + if *summary { + fmt.Fprintf(os.Stderr, "writing to stdout\n") + } + } else { + toF, err = os.Create(destPathname) + if err != nil { + bail(err) + } + defer func(ioc io.Closer) { + if err := ioc.Close(); err != nil { + bail(err) + } + }(toF) + if *summary { + fmt.Fprintf(os.Stderr, "writing to %s\n", flag.Arg(1)) + } + } + + // NOTE: Convert fromF to OCFReader + ocfr, err := goavro.NewOCFReader(fromF) + if err != nil { + bail(err) + } + + inputCompressionName := ocfr.CompressionName() + outputCompressionName := inputCompressionName + if *compressionName != "" { + outputCompressionName = *compressionName + } + + if *summary { + fmt.Fprintf(os.Stderr, "input compression algorithm: %s\n", inputCompressionName) + fmt.Fprintf(os.Stderr, "output compression algorithm: %s\n", outputCompressionName) + } + + // NOTE: Either use schema from reader, or attempt to use new schema + var outputSchema string + if *schemaPathname == "" { + outputSchema = ocfr.Codec().Schema() + } else { + schemaBytes, err := ioutil.ReadFile(*schemaPathname) + if err != nil { + bail(err) + } + outputSchema = string(schemaBytes) + } + + // NOTE: Convert toF to OCFWriter + ocfw, err := goavro.NewOCFWriter(goavro.OCFConfig{ + W: toF, + CompressionName: outputCompressionName, + Schema: outputSchema, + }) + if err != nil { + bail(err) + } + + if err := transcode(ocfr, ocfw); err != nil { + bail(err) + } +} + +func transcode(from *goavro.OCFReader, to *goavro.OCFWriter) error { + var blocksRead, blocksWritten, itemsRead int + + var block []interface{} + if *blockCount > 0 { + block = make([]interface{}, 0, *blockCount) + } + + for from.Scan() { + datum, err := from.Read() + if err != nil { + break + } + + itemsRead++ + block = append(block, datum) + + endOfBlock := from.RemainingBlockItems() == 0 + if endOfBlock { + blocksRead++ + if *verbose { + fmt.Fprintf(os.Stderr, "read block with %d items\n", len(block)) + } + } + + // NOTE: When blockCount is 0, user wants each destination block to have + // the same number of items as its corresponding source block. However, + // when blockCount is greater than 0, user wants specified block count + // sizes. + if (*blockCount == 0 && endOfBlock) || (*blockCount > 0 && len(block) == *blockCount) { + if err := writeBlock(to, block); err != nil { + return err + } + blocksWritten++ + block = block[:0] // set slice length to 0 in order to re-use allocated underlying array + } + } + + var err error + + // append all remaining items (condition can only be true used when *blockCount > 0) + if len(block) > 0 { + if err = writeBlock(to, block); err == nil { + blocksWritten++ + } + } + + // if no write error, then return any read error encountered + if err == nil { + err = from.Err() + } + + if *summary { + fmt.Fprintf(os.Stderr, "read %d items\n", itemsRead) + fmt.Fprintf(os.Stderr, "wrote %d blocks\n", blocksWritten) + } + + return err +} + +func writeBlock(to *goavro.OCFWriter, block []interface{}) error { + if *verbose { + fmt.Fprintf(os.Stderr, "writing block with %d items\n", len(block)) + } + return to.Append(block) +} diff --git a/fork/goavro/examples/avroheader/main.go b/fork/goavro/examples/avroheader/main.go new file mode 100644 index 0000000..c16b6f1 --- /dev/null +++ b/fork/goavro/examples/avroheader/main.go @@ -0,0 +1,110 @@ +package main + +import ( + "flag" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/linkedin/goavro/v2" +) + +var ( + showCount = flag.Bool("count", false, "show count of data items") + showSchema = flag.Bool("schema", false, "show data schema") +) + +func usage() { + executable, err := os.Executable() + if err != nil { + executable = os.Args[0] + } + base := filepath.Base(executable) + fmt.Fprintf(os.Stderr, "Usage of %s:\n", base) + fmt.Fprintf(os.Stderr, "\t%s [-count] [-schema] [file1.avro...]\n", base) + fmt.Fprintf(os.Stderr, "\tAs a special case, when there are no filename arguments, %s will read\n", base) + fmt.Fprintf(os.Stderr, "\tfrom its standard input.\n") + flag.PrintDefaults() + os.Exit(2) +} + +func main() { + flag.Parse() + + args := flag.Args() + + if len(args) == 0 { + stat, err := os.Stdin.Stat() + if err != nil { + bail(err) + } + if (stat.Mode() & os.ModeCharDevice) != 0 { + usage() + } + if err := headerFromReader(os.Stdin, ""); err != nil { + bail(err) + } + } + + for _, arg := range args { + fh, err := os.Open(arg) + if err != nil { + bail(err) + } + if len(args) > 1 { + arg += ": " + } else { + arg = "" + } + if err := headerFromReader(fh, arg); err != nil { + bail(err) + } + if err := fh.Close(); err != nil { + bail(err) + } + } +} + +func headerFromReader(ior io.Reader, prefix string) error { + ocfr, err := goavro.NewOCFReader(ior) + if err != nil { + return err + } + + fmt.Printf("%sCompression Algorithm (avro.codec): %q\n", prefix, ocfr.CompressionName()) + + if *showSchema { + fmt.Printf("%sSchema (avro.schema):\n%s\n", prefix, ocfr.Codec().Schema()) + } + + if !*showCount { + return nil + } + + var decoded, errors int + + for ocfr.Scan() { + _, err := ocfr.Read() + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + errors++ + continue + } + decoded++ + } + + if decoded > 0 { + fmt.Printf("%sSuccessfully decoded: %d\n", prefix, decoded) + } + if errors > 0 { + fmt.Printf("%sCannot decode: %d\n", prefix, errors) + } + + return ocfr.Err() +} + +func bail(err error) { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(1) +} diff --git a/fork/goavro/examples/nested/main.go b/fork/goavro/examples/nested/main.go new file mode 100644 index 0000000..1dccbf9 --- /dev/null +++ b/fork/goavro/examples/nested/main.go @@ -0,0 +1,172 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "reflect" + + "github.com/linkedin/goavro/v2" +) + +var ( + codec *goavro.Codec +) + +func init() { + + schema, err := ioutil.ReadFile("schema.avsc") + if err != nil { + panic(err) + } + + //Create Schema Once + codec, err = goavro.NewCodec(string(schema)) + if err != nil { + panic(err) + } +} + +func main() { + + //Sample Data + user := &User{ + FirstName: "John", + LastName: "Snow", + Address: &Address{ + Address1: "1106 Pennsylvania Avenue", + City: "Wilmington", + State: "DE", + Zip: 19806, + }, + } + + fmt.Printf("user in=%+v\n", user) + + ///Convert Binary From Native + binary, err := codec.BinaryFromNative(nil, user.ToStringMap()) + if err != nil { + panic(err) + } + + ///Convert Native from Binary + native, _, err := codec.NativeFromBinary(binary) + if err != nil { + panic(err) + } + + //Convert it back tp Native + userOut := StringMapToUser(native.(map[string]interface{})) + fmt.Printf("user out=%+v\n", userOut) + if ok := reflect.DeepEqual(user, userOut); !ok { + fmt.Fprintf(os.Stderr, "struct Compare Failed ok=%t\n", ok) + os.Exit(1) + } +} + +// User holds information about a user. +type User struct { + FirstName string + LastName string + Errors []string + Address *Address +} + +// Address holds information about an address. +type Address struct { + Address1 string + Address2 string + City string + State string + Zip int +} + +// ToStringMap returns a map representation of the User. +func (u *User) ToStringMap() map[string]interface{} { + datumIn := map[string]interface{}{ + "FirstName": string(u.FirstName), + "LastName": string(u.LastName), + } + + if len(u.Errors) > 0 { + datumIn["Errors"] = goavro.Union("array", u.Errors) + } else { + datumIn["Errors"] = goavro.Union("null", nil) + } + + if u.Address != nil { + addDatum := map[string]interface{}{ + "Address1": string(u.Address.Address1), + "City": string(u.Address.City), + "State": string(u.Address.State), + "Zip": int(u.Address.Zip), + } + if u.Address.Address2 != "" { + addDatum["Address2"] = goavro.Union("string", u.Address.Address2) + } else { + addDatum["Address2"] = goavro.Union("null", nil) + } + + //important need namespace and record name + datumIn["Address"] = goavro.Union("my.namespace.com.address", addDatum) + + } else { + datumIn["Address"] = goavro.Union("null", nil) + } + return datumIn +} + +// StringMapToUser returns a User from a map representation of the User. +func StringMapToUser(data map[string]interface{}) *User { + + ind := &User{} + for k, v := range data { + switch k { + case "FirstName": + if value, ok := v.(string); ok { + ind.FirstName = value + } + case "LastName": + if value, ok := v.(string); ok { + ind.LastName = value + } + case "Errors": + if value, ok := v.(map[string]interface{}); ok { + for _, item := range value["array"].([]interface{}) { + ind.Errors = append(ind.Errors, item.(string)) + } + } + case "Address": + if vmap, ok := v.(map[string]interface{}); ok { + //important need namespace and record name + if cookieSMap, ok := vmap["my.namespace.com.address"].(map[string]interface{}); ok { + add := &Address{} + for k, v := range cookieSMap { + switch k { + case "Address1": + if value, ok := v.(string); ok { + add.Address1 = value + } + case "Address2": + if value, ok := v.(string); ok { + add.Address2 = value + } + case "City": + if value, ok := v.(string); ok { + add.City = value + } + case "Zip": + if value, ok := v.(int); ok { + add.Zip = value + } + } + } + ind.Address = add + } + } + } + + } + return ind + +} diff --git a/fork/goavro/examples/nested/schema.avsc b/fork/goavro/examples/nested/schema.avsc new file mode 100644 index 0000000..075c6b2 --- /dev/null +++ b/fork/goavro/examples/nested/schema.avsc @@ -0,0 +1,22 @@ +{ + "namespace": "my.namespace.com", + "type": "record", + "name": "indentity", + "fields": [ + { "name": "FirstName", "type": "string"}, + { "name": "LastName", "type": "string"}, + { "name": "Errors", "type": ["null", {"type":"array", "items":"string"}], "default": null }, + { "name": "Address", "type": ["null",{ + "namespace": "my.namespace.com", + "type": "record", + "name": "address", + "fields": [ + { "name": "Address1", "type": "string" }, + { "name": "Address2", "type": ["null", "string"], "default": null }, + { "name": "City", "type": "string" }, + { "name": "State", "type": "string" }, + { "name": "Zip", "type": "int" } + ] + }],"default":null} + ] + } \ No newline at end of file diff --git a/fork/goavro/examples/roundtrip/main.go b/fork/goavro/examples/roundtrip/main.go new file mode 100644 index 0000000..19139da --- /dev/null +++ b/fork/goavro/examples/roundtrip/main.go @@ -0,0 +1,176 @@ +package main + +import ( + "bufio" + bin "encoding/binary" + hex "encoding/hex" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + + "github.com/linkedin/goavro/v2" +) + +// roundtrip is a tool for checking avro +// +// incoming data is assumed to be standard json +// incoming json is required to be one json object per line +// use `jq -c .` if you need to. get it into one line +// +// you can write out your avro in binary form and stop there +// which is useful for cases where you might want to send it off into other tools +// +// you can also do a roundtrip of decode/encode +// which allows you to see if your avro schema matches your expectations +// +// If you want to use an encoded schemaid then specify a schemid with -sid +// it will be encoded per a common standard (one null byte, 16 bytes of schemaid) +// Its NOT the standard SOE +// SOE should be added +// Probably OCF should be added too +// +// EXAMPLE +// +// kubectl get events -w -o json | jq -c . | ./roundtrip -sid aa6b1ca0e1ee2d885bfbc747f4a4011b -avsc event-schema.json ) -rt + +func MakeAvroHeader(schemaid string) (header []byte, err error) { + dst, err := hex.DecodeString(schemaid) + if err != nil { + return + } + header = append(header, byte(0)) + header = append(header, dst...) + return +} +func main() { + + var avsc = flag.String("avsc", "", "the avro schema") + var data = flag.String("data", "-", "(default stdin) the data that corresponds to the avro schema or error - ONE LINE PER DATA ITEM") + var schemaid = flag.String("sid", "", "the schemaid which is normally the md5hash of rht schema itself") + var roundtrip = flag.Bool("rt", false, "do full round trip to try to rebuild the original data string") + var xxd = flag.String("bin", "", "write out the binary data to this file - look at it with xxd if you want to") + var appendBin = flag.Bool("append", false, "append to the output binary file instead of trunc") + + flag.Parse() + + _avsc, err := ioutil.ReadFile(*avsc) + if err != nil { + panic(fmt.Sprintf("Failed to read avsc file:%s:error:%v:", *avsc, err)) + } + + codec, err := goavro.NewCodecForStandardJSON(string(_avsc)) + if err != nil { + panic(err) + } + + var _data io.Reader + if *data == "-" { + _data = os.Stdin + } else { + file, err := os.Open(*data) + if err != nil { + panic(fmt.Sprintf("Failed to open data file:%s:error:%v:", *data, err)) + } + _data = bufio.NewReader(file) + defer file.Close() + } + + binOut := struct { + file *os.File + do bool + }{} + if len(*xxd) > 0 { + bits := os.O_WRONLY | os.O_CREATE + if *appendBin { + bits |= os.O_APPEND + } else { + bits |= os.O_TRUNC + } + + binOut.file, err = os.OpenFile(*xxd, bits, 0600) + if err != nil { + panic(err) + } + defer binOut.file.Close() + binOut.do = true + } + + scanner := bufio.NewScanner(_data) + + for scanner.Scan() { + + dat := scanner.Text() + if len(dat) == 0 { + fmt.Println("skipping empty line") + continue + } + + fmt.Println("RT in") + fmt.Println(dat) + + textual := []byte(dat) + + fmt.Printf("encoding for schemaid:%s:\n", *schemaid) + avroNative, _, err := codec.NativeFromTextual(textual) + + if err != nil { + fmt.Println(dat) + panic(err) + } + + header, err := MakeAvroHeader(*schemaid) + if err != nil { + fmt.Println(string(textual)) + panic(err) + } + + avrobin, err := codec.BinaryFromNative(nil, avroNative) + if err != nil { + fmt.Println(dat) + panic(err) + } + + // trying to minimize operations within the loop + // so do only a quick boolean check here + if binOut.do { + for _, buf := range [][]byte{header, avrobin} { + err = bin.Write(binOut.file, bin.LittleEndian, buf) + if err != nil { + fmt.Println(dat) + panic(err) + } + } + } + + if *roundtrip { + // this will scramble the order + // since it makes new go maps + // when it takes the binary into native + rtnativeval, _, err := codec.NativeFromBinary(avrobin) + if err != nil { + fmt.Println(dat) + panic(err) + } + + // Convert native Go form to textual Avro data + textual, err = codec.TextualFromNative(nil, rtnativeval) + if err != nil { + fmt.Println(dat) + panic(err) + } + + fmt.Println("RT out") + fmt.Println(string(textual)) + } + + } + if err := scanner.Err(); err != nil { + fmt.Println("scanner error") + panic(err) + } + + fmt.Println("Done with loop - no more data") + +} diff --git a/fork/goavro/examples/soe/main.go b/fork/goavro/examples/soe/main.go new file mode 100644 index 0000000..21c76ce --- /dev/null +++ b/fork/goavro/examples/soe/main.go @@ -0,0 +1,84 @@ +package main + +import ( + "fmt" + + "github.com/linkedin/goavro/v2" +) + +func main() { + codex := initCodex() + + err := decode(codex, []byte("\xC3\x01"+"\x8F\x5C\x39\x3F\x1A\xD5\x75\x72"+"\x06")) + if err != nil { + panic(err) + } + + err = decode(codex, []byte("\xC3\x01"+"\xC7\x03\x45\x63\x72\x48\x01\x8F"+"\x0ahello")) + if err != nil { + panic(err) + } +} + +// initCodex returns a codex with a small handful of example Codec instances. +func initCodex() map[uint64]*goavro.Codec { + codex := make(map[uint64]*goavro.Codec) + + for _, primitive := range []string{"int", "long", "boolean", "float", "double", "string"} { + codec, err := goavro.NewCodec(`"` + primitive + `"`) + if err != nil { + panic(err) + } + codex[codec.Rabin] = codec + } + + return codex +} + +// decode attempts to decode the bytes in buf using one of the Codec instances +// in codex. The buf must start with the single-object encoding prefix, +// followed by the unsigned 64-bit Rabin fingerprint of the canonical schema +// used to encode the datum, finally followed by the encoded bytes. This is a +// simplified example of fetching the fingerprint from the SOE buffer, using +// that fingerprint to select a Codec from a dictionary of Codec instances, +// called codex in this case, and finally sends the buf to be decoded by that +// Codec. +func decode(codex map[uint64]*goavro.Codec, buf []byte) error { + // Perform a sanity check on the buffer, then return the Rabin fingerprint + // of the schema used to encode the data. + fingerprint, newBuf, err := goavro.FingerprintFromSOE(buf) + if err != nil { + panic(err) + return err + } + + // Get a previously stored Codec from the codex map. + codec, ok := codex[fingerprint] + if !ok { + return fmt.Errorf("unknown codec: %#x", fingerprint) + } + + // Use the fetched Codec to decode the buffer as a SOE. + var datum interface{} + + // Both of the following branches work, but provided to illustrate two + // use-cases. + if true { + // Faster because SOE magic prefix and schema fingerprint already + // checked and used to fetch the Codec. Just need to decode the binary + // bytes remaining after the prefix were removed. + datum, _, err = codec.NativeFromBinary(newBuf) + } else { + // This way re-checks the SOE magic prefix and Codec fingerprint, doing + // repetitive work, but provided as an example for cases when there is + // only a single schema, a single Codec, and you do not use + // the FingerprintFromSOE function above. + datum, _, err = codec.NativeFromSingle(buf) + } + if err != nil { + panic(err) + } + + _, err = fmt.Println(datum) + return err +} diff --git a/fork/goavro/examples/splice/main.go b/fork/goavro/examples/splice/main.go new file mode 100644 index 0000000..38584a9 --- /dev/null +++ b/fork/goavro/examples/splice/main.go @@ -0,0 +1,92 @@ +package main + +import ( + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/linkedin/goavro/v2" +) + +func bail(err error) { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(1) +} + +func usage() { + executable, err := os.Executable() + if err != nil { + executable = os.Args[0] + } + base := filepath.Base(executable) + fmt.Fprintf(os.Stderr, "Usage of %s:\n", base) + fmt.Fprintf(os.Stderr, "\t%s [-compression null|deflate|snappy] schema.avsc input.dat output.avro\n", base) + flag.PrintDefaults() + os.Exit(2) +} + +func main() { + compressionName := flag.String("compression", "null", "compression codec ('null', 'deflate', 'snappy'; default: 'null')") + flag.Parse() + + if len(flag.Args()) != 3 { + usage() + } + + schemaBytes, err := ioutil.ReadFile(flag.Arg(0)) + if err != nil { + bail(err) + } + + codec, err := goavro.NewCodec(string(schemaBytes)) + if err != nil { + bail(err) + } + + dataBytes, err := ioutil.ReadFile(flag.Arg(1)) + if err != nil { + bail(err) + } + + fh, err := os.Create(flag.Arg(2)) + if err != nil { + bail(err) + } + defer func(ioc io.Closer) { + if err := ioc.Close(); err != nil { + bail(err) + } + }(fh) + + ocfw, err := goavro.NewOCFWriter(goavro.OCFConfig{ + W: fh, + Codec: codec, + CompressionName: *compressionName, + }) + if err != nil { + bail(err) + } + + var datum interface{} + + for len(dataBytes) > 0 { + datum, dataBytes, err = codec.NativeFromBinary(dataBytes) + if err != nil { + if err == io.EOF { + err = nil + break + } + bail(err) + } + if err = ocfw.Append([]interface{}{datum}); err != nil { + bail(err) + } + } + + if err != nil { + bail(err) + } +} diff --git a/fork/goavro/fixed.go b/fork/goavro/fixed.go new file mode 100644 index 0000000..d3310a9 --- /dev/null +++ b/fork/goavro/fixed.go @@ -0,0 +1,111 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "fmt" + "strconv" +) + +// Fixed does not have child objects, therefore whatever namespace it defines is +// just to store its name in the symbol table. +func makeFixedCodec(st map[string]*Codec, enclosingNamespace string, schemaMap map[string]interface{}) (*Codec, error) { + c, err := registerNewNamedCodec(st, schemaMap, enclosingNamespace) + if err != nil { + return nil, fmt.Errorf("fixed ought to have valid name: %s", err) + } + size, err := sizeFromSchemaMap(c.typeName, schemaMap) + if err != nil { + return nil, err + } + + c.nativeFromBinary = func(buf []byte) (interface{}, []byte, error) { + if buflen := uint(len(buf)); size > buflen { + return nil, nil, fmt.Errorf("cannot decode binary fixed %q: schema size exceeds remaining buffer size: %d > %d (short buffer)", c.typeName, size, buflen) + } + return buf[:size], buf[size:], nil + } + + c.binaryFromNative = func(buf []byte, datum interface{}) ([]byte, error) { + var someBytes []byte + switch d := datum.(type) { + case []byte: + someBytes = d + case string: + someBytes = []byte(d) + default: + return nil, fmt.Errorf("cannot encode binary fixed %q: expected []byte or string; received: %T", c.typeName, datum) + } + if count := uint(len(someBytes)); count != size { + return nil, fmt.Errorf("cannot encode binary fixed %q: datum size ought to equal schema size: %d != %d", c.typeName, count, size) + } + return append(buf, someBytes...), nil + } + + c.nativeFromTextual = func(buf []byte) (interface{}, []byte, error) { + if buflen := uint(len(buf)); size > buflen { + return nil, nil, fmt.Errorf("cannot decode textual fixed %q: schema size exceeds remaining buffer size: %d > %d (short buffer)", c.typeName, size, buflen) + } + var datum interface{} + var err error + datum, buf, err = bytesNativeFromTextual(buf) + if err != nil { + return nil, buf, err + } + datumBytes := datum.([]byte) + if count := uint(len(datumBytes)); count != size { + return nil, nil, fmt.Errorf("cannot decode textual fixed %q: datum size ought to equal schema size: %d != %d", c.typeName, count, size) + } + return datum, buf, err + } + + c.textualFromNative = func(buf []byte, datum interface{}) ([]byte, error) { + var someBytes []byte + switch d := datum.(type) { + case []byte: + someBytes = d + case string: + someBytes = []byte(d) + default: + return nil, fmt.Errorf("cannot encode textual fixed %q: expected []byte or string; received: %T", c.typeName, datum) + } + if count := uint(len(someBytes)); count != size { + return nil, fmt.Errorf("cannot encode textual fixed %q: datum size ought to equal schema size: %d != %d", c.typeName, count, size) + } + return bytesTextualFromNative(buf, someBytes) + } + + return c, nil +} + +func sizeFromSchemaMap(typeName *name, schemaMap map[string]interface{}) (uint, error) { + // Fixed type must have size + sizeRaw, ok := schemaMap["size"] + if !ok { + return 0, fmt.Errorf("fixed %q ought to have size key", typeName) + } + var size uint + switch val := sizeRaw.(type) { + case string: + s, err := strconv.ParseUint(val, 10, 0) + if err != nil { + return 0, fmt.Errorf("fixed %q size ought to be number greater than zero: %v", typeName, sizeRaw) + } + size = uint(s) + case float64: + if val <= 0 { + return 0, fmt.Errorf("fixed %q size ought to be number greater than zero: %v", typeName, sizeRaw) + } + size = uint(val) + default: + return 0, fmt.Errorf("fixed %q size ought to be number greater than zero: %v", typeName, sizeRaw) + } + return size, nil +} diff --git a/fork/goavro/fixed_test.go b/fork/goavro/fixed_test.go new file mode 100644 index 0000000..4eb2379 --- /dev/null +++ b/fork/goavro/fixed_test.go @@ -0,0 +1,86 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "testing" +) + +func TestSchemaFixed(t *testing.T) { + testSchemaValid(t, `{"type": "fixed", "size": 16, "name": "md5"}`) + testSchemaValid(t, `{"type":"fixed","name":"f1","size":"16"}`) +} + +func TestFixedName(t *testing.T) { + testSchemaInvalid(t, `{"type":"fixed","size":16}`, "Fixed ought to have valid name: schema ought to have name key") + testSchemaInvalid(t, `{"type":"fixed","name":3}`, "Fixed ought to have valid name: schema name ought to be non-empty string") + testSchemaInvalid(t, `{"type":"fixed","name":""}`, "Fixed ought to have valid name: schema name ought to be non-empty string") + testSchemaInvalid(t, `{"type":"fixed","name":"&foo","size":16}`, "Fixed ought to have valid name: schema name ought to start with") + testSchemaInvalid(t, `{"type":"fixed","name":"foo&","size":16}`, "Fixed ought to have valid name: schema name ought to have second and remaining") +} + +func TestFixedSize(t *testing.T) { + testSchemaInvalid(t, `{"type":"fixed","name":"f1"}`, `Fixed "f1" ought to have size key`) + testSchemaInvalid(t, `{"type":"fixed","name":"f1","size":-1}`, `Fixed "f1" size ought to be number greater than zero`) + testSchemaInvalid(t, `{"type":"fixed","name":"f1","size":0}`, `Fixed "f1" size ought to be number greater than zero`) +} + +func TestFixedDecodeBufferUnderflow(t *testing.T) { + testBinaryDecodeFail(t, `{"type":"fixed","name":"md5","size":16}`, nil, "short buffer") +} + +func TestFixedDecodeWithExtra(t *testing.T) { + c, err := NewCodec(`{"type":"fixed","name":"foo","size":4}`) + if err != nil { + t.Errorf("GOT: %#v; WANT: %#v", err, nil) + } + val, buf, err := c.NativeFromBinary([]byte("abcdefgh")) + if actual, expected := string(val.([]byte)), "abcd"; actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } + if actual, expected := string(buf), "efgh"; actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } + if err != nil { + t.Errorf("GOT: %#v; WANT: %#v", err, nil) + } +} + +func TestFixedEncodeUnsupportedType(t *testing.T) { + testBinaryEncodeFailBadDatumType(t, `{"type":"fixed","name":"foo","size":4}`, 13) +} + +func TestFixedEncodeWrongSize(t *testing.T) { + testBinaryEncodeFail(t, `{"type":"fixed","name":"foo","size":4}`, []byte("abcde"), "datum size ought to equal schema size") + testBinaryEncodeFail(t, `{"type":"fixed","name":"foo","size":4}`, []byte("abc"), "datum size ought to equal schema size") +} + +func TestFixedEncode(t *testing.T) { + testBinaryCodecPass(t, `{"type":"fixed","name":"foo","size":4}`, []byte("abcd"), []byte("abcd")) +} + +func TestFixedTextCodec(t *testing.T) { + schema := `{"type":"fixed","name":"f1","size":4}` + testTextDecodeFail(t, schema, []byte(`"\u0001\u0002\u0003"`), "datum size ought to equal schema size") + testTextDecodeFail(t, schema, []byte(`"\u0001\u0002\u0003\u0004\u0005"`), "datum size ought to equal schema size") + testTextEncodeFail(t, schema, []byte{1, 2, 3}, "datum size ought to equal schema size") + testTextEncodeFail(t, schema, []byte{1, 2, 3, 4, 5}, "datum size ought to equal schema size") + testTextEncodePass(t, schema, []byte{1, 2, 3, 4}, []byte(`"\u0001\u0002\u0003\u0004"`)) +} + +func TestFixedCodecAcceptsString(t *testing.T) { + schema := `{"type":"fixed","name":"f1","size":4}` + t.Run("binary", func(t *testing.T) { + testBinaryEncodePass(t, schema, "abcd", []byte(`abcd`)) + }) + t.Run("text", func(t *testing.T) { + testTextEncodePass(t, schema, "abcd", []byte(`"abcd"`)) + }) +} diff --git a/fork/goavro/fixtures/bad-header.avro b/fork/goavro/fixtures/bad-header.avro new file mode 100644 index 0000000..0144893 --- /dev/null +++ b/fork/goavro/fixtures/bad-header.avro @@ -0,0 +1 @@ +Obj diff --git a/fork/goavro/fixtures/blockCountExceedsMaxBlockCount.avro b/fork/goavro/fixtures/blockCountExceedsMaxBlockCount.avro new file mode 100644 index 0000000..48f0ff0 Binary files /dev/null and b/fork/goavro/fixtures/blockCountExceedsMaxBlockCount.avro differ diff --git a/fork/goavro/fixtures/blockSizeExceedsMaxBlockSize.avro b/fork/goavro/fixtures/blockSizeExceedsMaxBlockSize.avro new file mode 100644 index 0000000..af3f5fd Binary files /dev/null and b/fork/goavro/fixtures/blockSizeExceedsMaxBlockSize.avro differ diff --git a/fork/goavro/fixtures/blockSizeNotGreaterThanZero.avro b/fork/goavro/fixtures/blockSizeNotGreaterThanZero.avro new file mode 100644 index 0000000..49ffbe1 Binary files /dev/null and b/fork/goavro/fixtures/blockSizeNotGreaterThanZero.avro differ diff --git a/fork/goavro/fixtures/cannotDiscardBlockBytes.avro b/fork/goavro/fixtures/cannotDiscardBlockBytes.avro new file mode 100644 index 0000000..b0f23e5 Binary files /dev/null and b/fork/goavro/fixtures/cannotDiscardBlockBytes.avro differ diff --git a/fork/goavro/fixtures/cannotReadBlockSize.avro b/fork/goavro/fixtures/cannotReadBlockSize.avro new file mode 100644 index 0000000..6ae5bf1 Binary files /dev/null and b/fork/goavro/fixtures/cannotReadBlockSize.avro differ diff --git a/fork/goavro/fixtures/cannotReadSyncMarker.avro b/fork/goavro/fixtures/cannotReadSyncMarker.avro new file mode 100644 index 0000000..d7d7dbe Binary files /dev/null and b/fork/goavro/fixtures/cannotReadSyncMarker.avro differ diff --git a/fork/goavro/fixtures/firstBlockCountNotGreaterThanZero.avro b/fork/goavro/fixtures/firstBlockCountNotGreaterThanZero.avro new file mode 100644 index 0000000..807ff98 Binary files /dev/null and b/fork/goavro/fixtures/firstBlockCountNotGreaterThanZero.avro differ diff --git a/fork/goavro/fixtures/quickstop-deflate.avro b/fork/goavro/fixtures/quickstop-deflate.avro new file mode 100644 index 0000000..b9dd2c9 Binary files /dev/null and b/fork/goavro/fixtures/quickstop-deflate.avro differ diff --git a/fork/goavro/fixtures/quickstop-null.avro b/fork/goavro/fixtures/quickstop-null.avro new file mode 100644 index 0000000..5b3bc73 Binary files /dev/null and b/fork/goavro/fixtures/quickstop-null.avro differ diff --git a/fork/goavro/fixtures/quickstop-snappy.avro b/fork/goavro/fixtures/quickstop-snappy.avro new file mode 100644 index 0000000..702a5a1 Binary files /dev/null and b/fork/goavro/fixtures/quickstop-snappy.avro differ diff --git a/fork/goavro/fixtures/quickstop.avsc b/fork/goavro/fixtures/quickstop.avsc new file mode 100644 index 0000000..c45b3d0 --- /dev/null +++ b/fork/goavro/fixtures/quickstop.avsc @@ -0,0 +1 @@ +{"fields":[{"name":"ID","type":{"type":"long"}},{"name":"First","type":{"type":"string"}},{"name":"Last","type":{"type":"string"}},{"name":"Phone","type":{"type":"string"}},{"name":"Age","type":{"type":"int"}}],"name":"Person","type":"record"} \ No newline at end of file diff --git a/fork/goavro/fixtures/secondBlockCountZero.avro b/fork/goavro/fixtures/secondBlockCountZero.avro new file mode 100644 index 0000000..dd16063 Binary files /dev/null and b/fork/goavro/fixtures/secondBlockCountZero.avro differ diff --git a/fork/goavro/fixtures/syncMarkerMismatch.avro b/fork/goavro/fixtures/syncMarkerMismatch.avro new file mode 100644 index 0000000..d99da8d Binary files /dev/null and b/fork/goavro/fixtures/syncMarkerMismatch.avro differ diff --git a/fork/goavro/fixtures/temp0.avro b/fork/goavro/fixtures/temp0.avro new file mode 100644 index 0000000..e69de29 diff --git a/fork/goavro/fixtures/temp1.avro b/fork/goavro/fixtures/temp1.avro new file mode 100644 index 0000000..c210f2d Binary files /dev/null and b/fork/goavro/fixtures/temp1.avro differ diff --git a/fork/goavro/fixtures/temp2.avro b/fork/goavro/fixtures/temp2.avro new file mode 100644 index 0000000..1a89516 Binary files /dev/null and b/fork/goavro/fixtures/temp2.avro differ diff --git a/fork/goavro/fixtures/temp3.avro b/fork/goavro/fixtures/temp3.avro new file mode 100644 index 0000000..4afbfcc Binary files /dev/null and b/fork/goavro/fixtures/temp3.avro differ diff --git a/fork/goavro/fixtures/temp4.avro b/fork/goavro/fixtures/temp4.avro new file mode 100644 index 0000000..420ab68 Binary files /dev/null and b/fork/goavro/fixtures/temp4.avro differ diff --git a/fork/goavro/floatingPoint.go b/fork/goavro/floatingPoint.go new file mode 100644 index 0000000..57c1c86 --- /dev/null +++ b/fork/goavro/floatingPoint.go @@ -0,0 +1,310 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "math" + "strconv" +) + +const ( + doubleEncodedLength = 8 // double requires 8 bytes + floatEncodedLength = 4 // float requires 4 bytes +) + +//////////////////////////////////////// +// Binary Decode +//////////////////////////////////////// + +func doubleNativeFromBinary(buf []byte) (interface{}, []byte, error) { + if len(buf) < doubleEncodedLength { + return nil, nil, fmt.Errorf("cannot decode binary double: %s", io.ErrShortBuffer) + } + return math.Float64frombits(binary.LittleEndian.Uint64(buf[:doubleEncodedLength])), buf[doubleEncodedLength:], nil +} + +func floatNativeFromBinary(buf []byte) (interface{}, []byte, error) { + if len(buf) < floatEncodedLength { + return nil, nil, fmt.Errorf("cannot decode binary float: %s", io.ErrShortBuffer) + } + return math.Float32frombits(binary.LittleEndian.Uint32(buf[:floatEncodedLength])), buf[floatEncodedLength:], nil +} + +//////////////////////////////////////// +// Binary Encode +//////////////////////////////////////// + +func doubleBinaryFromNative(buf []byte, datum interface{}) ([]byte, error) { + var value float64 + switch v := datum.(type) { + case float64: + value = v + case float32: + value = float64(v) + case int: + if value = float64(v); int(value) != v { + return nil, fmt.Errorf("cannot encode binary double: provided Go int would lose precision: %d", v) + } + case int64: + if value = float64(v); int64(value) != v { + return nil, fmt.Errorf("cannot encode binary double: provided Go int64 would lose precision: %d", v) + } + case int32: + if value = float64(v); int32(value) != v { + return nil, fmt.Errorf("cannot encode binary double: provided Go int32 would lose precision: %d", v) + } + case string: + valuetmp, err := strconv.ParseFloat(v, 32) + if err != nil { + return nil, fmt.Errorf("cannot encode binary float: provided Go string would lose precision: %s", v) + } else { + value = valuetmp + } + default: + return nil, fmt.Errorf("cannot encode binary double: expected: Go numeric; received: %T", datum) + } + buf = append(buf, 0, 0, 0, 0, 0, 0, 0, 0) + binary.LittleEndian.PutUint64(buf[len(buf)-doubleEncodedLength:], math.Float64bits(value)) + return buf, nil +} + +func floatBinaryFromNative(buf []byte, datum interface{}) ([]byte, error) { + var value float32 + switch v := datum.(type) { + case float32: + value = v + case float64: + // Assume runtime can cast special floats correctly, and if there is a + // loss of precision from float64 and float32, that should be expected + // or at least understood by the client. + value = float32(v) + case int: + if value = float32(v); int(value) != v { + return nil, fmt.Errorf("cannot encode binary float: provided Go int would lose precision: %d", v) + } + case int64: + if value = float32(v); int64(value) != v { + return nil, fmt.Errorf("cannot encode binary float: provided Go int64 would lose precision: %d", v) + } + case int32: + if value = float32(v); int32(value) != v { + return nil, fmt.Errorf("cannot encode binary float: provided Go int32 would lose precision: %d", v) + } + case string: + valuetmp, err := strconv.ParseFloat(v, 32) + if err != nil { + return nil, fmt.Errorf("cannot encode binary float: provided Go string would lose precision: %s", v) + } else { + value = float32(valuetmp) + } + default: + return nil, fmt.Errorf("cannot encode binary float: expected: Go numeric; received: %T", datum) + } + // return floatingBinaryEncoder(buf, uint64(math.Float32bits(value)), floatEncodedLength) + buf = append(buf, 0, 0, 0, 0) + binary.LittleEndian.PutUint32(buf[len(buf)-floatEncodedLength:], uint32(math.Float32bits(value))) + return buf, nil +} + +//////////////////////////////////////// +// Text Decode +//////////////////////////////////////// + +func doubleNativeFromTextual(buf []byte) (interface{}, []byte, error) { + return floatingTextDecoder(buf, 64) +} + +func floatNativeFromTextual(buf []byte) (interface{}, []byte, error) { + return floatingTextDecoder(buf, 32) +} + +func floatingTextDecoder(buf []byte, bitSize int) (interface{}, []byte, error) { + buflen := len(buf) + if buflen >= 4 { + if bytes.Equal(buf[:4], []byte("null")) { + return math.NaN(), buf[4:], nil + } + if buflen >= 5 { + if bytes.Equal(buf[:5], []byte("1e999")) { + return math.Inf(1), buf[5:], nil + } + if buflen >= 6 { + if bytes.Equal(buf[:6], []byte("-1e999")) { + return math.Inf(-1), buf[6:], nil + } + } + } + } + index, err := numberLength(buf, true) // NOTE: floatAllowed = true + if err != nil { + return nil, nil, err + } + datum, err := strconv.ParseFloat(string(buf[:index]), bitSize) + if err != nil { + return nil, nil, err + } + if bitSize == 32 { + return float32(datum), buf[index:], nil + } + return datum, buf[index:], nil +} + +func numberLength(buf []byte, floatAllowed bool) (int, error) { + // ALGORITHM: increment index as long as bytes are valid for number state engine. + var index, buflen, count int + var b byte + + // STATE 0: begin, optional: - + if buflen = len(buf); index == buflen { + return 0, io.ErrShortBuffer + } + if buf[index] == '-' { + if index++; index == buflen { + return 0, io.ErrShortBuffer + } + } + // STATE 1: if 0, goto 2; otherwise if 1-9, goto 3; otherwise bail + if b = buf[index]; b == '0' { + if index++; index == buflen { + return index, nil // valid number + } + } else if b >= '1' && b <= '9' { + if index++; index == buflen { + return index, nil // valid number + } + // STATE 3: absorb zero or more digits + for { + if b = buf[index]; b < '0' || b > '9' { + break + } + if index++; index == buflen { + return index, nil // valid number + } + } + } else { + return 0, fmt.Errorf("unexpected byte: %q", b) + } + if floatAllowed { + // STATE 2: if ., goto 4; otherwise goto 5 + if buf[index] == '.' { + if index++; index == buflen { + return 0, io.ErrShortBuffer + } + // STATE 4: absorb one or more digits + for { + if b = buf[index]; b < '0' || b > '9' { + break + } + count++ + if index++; index == buflen { + return index, nil // valid number + } + } + if count == 0 { + // did not get at least one digit + return 0, fmt.Errorf("unexpected byte: %q", b) + } + } + // STATE 5: if e|e, goto 6; otherwise goto 7 + if b = buf[index]; b == 'e' || b == 'E' { + if index++; index == buflen { + return 0, io.ErrShortBuffer + } + // STATE 6: if -|+, goto 8; otherwise goto 8 + if b = buf[index]; b == '+' || b == '-' { + if index++; index == buflen { + return 0, io.ErrShortBuffer + } + } + // STATE 8: absorb one or more digits + count = 0 + for { + if b = buf[index]; b < '0' || b > '9' { + break + } + count++ + if index++; index == buflen { + return index, nil // valid number + } + } + if count == 0 { + // did not get at least one digit + return 0, fmt.Errorf("unexpected byte: %q", b) + } + } + } + // STATE 7: end + return index, nil +} + +//////////////////////////////////////// +// Text Encode +//////////////////////////////////////// + +func floatTextualFromNative(buf []byte, datum interface{}) ([]byte, error) { + return floatingTextEncoder(buf, datum, 32) +} + +func doubleTextualFromNative(buf []byte, datum interface{}) ([]byte, error) { + return floatingTextEncoder(buf, datum, 64) +} + +func floatingTextEncoder(buf []byte, datum interface{}, bitSize int) ([]byte, error) { + var isFloat bool + var someFloat64 float64 + var someInt64 int64 + switch v := datum.(type) { + case float32: + isFloat = true + someFloat64 = float64(v) + case float64: + isFloat = true + someFloat64 = v + case int: + if someInt64 = int64(v); int(someInt64) != v { + if bitSize == 64 { + return nil, fmt.Errorf("cannot encode textual double: provided Go int would lose precision: %d", v) + } + return nil, fmt.Errorf("cannot encode textual float: provided Go int would lose precision: %d", v) + } + case int64: + someInt64 = v + case int32: + if someInt64 = int64(v); int32(someInt64) != v { + if bitSize == 64 { + return nil, fmt.Errorf("cannot encode textual double: provided Go int32 would lose precision: %d", v) + } + return nil, fmt.Errorf("cannot encode textual float: provided Go int32 would lose precision: %d", v) + } + default: + if bitSize == 64 { + return nil, fmt.Errorf("cannot encode textual double: expected: Go numeric; received: %T", datum) + } + return nil, fmt.Errorf("cannot encode textual float: expected: Go numeric; received: %T", datum) + } + + if isFloat { + if math.IsNaN(someFloat64) { + return append(buf, "null"...), nil + } + if math.IsInf(someFloat64, 1) { + return append(buf, "1e999"...), nil + } + if math.IsInf(someFloat64, -1) { + return append(buf, "-1e999"...), nil + } + return strconv.AppendFloat(buf, someFloat64, 'g', -1, bitSize), nil + } + return strconv.AppendInt(buf, someInt64, 10), nil +} diff --git a/fork/goavro/floatingPoint_test.go b/fork/goavro/floatingPoint_test.go new file mode 100644 index 0000000..baeda02 --- /dev/null +++ b/fork/goavro/floatingPoint_test.go @@ -0,0 +1,75 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "math" + "testing" +) + +func TestSchemaPrimitiveCodecDouble(t *testing.T) { + testSchemaPrimativeCodec(t, `"double"`) +} + +func TestPrimitiveDoubleBinary(t *testing.T) { + testBinaryEncodeFailBadDatumType(t, `"double"`, "some string") + testBinaryDecodeFailShortBuffer(t, `"double"`, []byte("\x00\x00\x00\x00\x00\x00\xf0")) + testBinaryCodecPass(t, `"double"`, 3.5, []byte("\x00\x00\x00\x00\x00\x00\f@")) + testBinaryCodecPass(t, `"double"`, math.Inf(-1), []byte("\x00\x00\x00\x00\x00\x00\xf0\xff")) + testBinaryCodecPass(t, `"double"`, math.Inf(1), []byte("\x00\x00\x00\x00\x00\x00\xf0\u007f")) + testBinaryCodecPass(t, `"double"`, math.NaN(), []byte("\x01\x00\x00\x00\x00\x00\xf8\u007f")) +} + +func TestPrimitiveDoubleText(t *testing.T) { + testTextDecodeFailShortBuffer(t, `"double"`, []byte("")) + testTextDecodeFailShortBuffer(t, `"double"`, []byte("-")) + + testTextCodecPass(t, `"double"`, -12.3, []byte("-12.3")) + testTextCodecPass(t, `"double"`, -0.5, []byte("-0.5")) + testTextCodecPass(t, `"double"`, -3.5, []byte("-3.5")) + testTextCodecPass(t, `"double"`, 0, []byte("0")) + testTextCodecPass(t, `"double"`, 0.5, []byte("0.5")) + testTextCodecPass(t, `"double"`, 1, []byte("1")) + testTextCodecPass(t, `"double"`, 19.7, []byte("19.7")) + testTextCodecPass(t, `"double"`, math.Inf(-1), []byte("-1e999")) + testTextCodecPass(t, `"double"`, math.Inf(1), []byte("1e999")) + testTextCodecPass(t, `"double"`, math.NaN(), []byte("null")) + testTextDecodePass(t, `"double"`, math.Copysign(0, -1), []byte("-0")) +} + +func TestSchemaPrimitiveCodecFloat(t *testing.T) { + testSchemaPrimativeCodec(t, `"float"`) +} + +func TestPrimitiveFloatBinary(t *testing.T) { + testBinaryEncodeFailBadDatumType(t, `"float"`, "some string") + testBinaryDecodeFailShortBuffer(t, `"float"`, []byte("\x00\x00\x80")) + testBinaryCodecPass(t, `"float"`, 3.5, []byte("\x00\x00\x60\x40")) + testBinaryCodecPass(t, `"float"`, math.Inf(-1), []byte("\x00\x00\x80\xff")) + testBinaryCodecPass(t, `"float"`, math.Inf(1), []byte("\x00\x00\x80\u007f")) + testBinaryCodecPass(t, `"float"`, math.NaN(), []byte("\x00\x00\xc0\u007f")) +} + +func TestPrimitiveFloatText(t *testing.T) { + testTextDecodeFailShortBuffer(t, `"float"`, []byte("")) + testTextDecodeFailShortBuffer(t, `"float"`, []byte("-")) + + testTextCodecPass(t, `"float"`, -12.3, []byte("-12.3")) + testTextCodecPass(t, `"float"`, -0.5, []byte("-0.5")) + testTextCodecPass(t, `"float"`, -3.5, []byte("-3.5")) + testTextCodecPass(t, `"float"`, 0, []byte("0")) + testTextCodecPass(t, `"float"`, 0.5, []byte("0.5")) + testTextCodecPass(t, `"float"`, 1, []byte("1")) + testTextCodecPass(t, `"float"`, 19.7, []byte("19.7")) + testTextCodecPass(t, `"float"`, math.Inf(-1), []byte("-1e999")) + testTextCodecPass(t, `"float"`, math.Inf(1), []byte("1e999")) + testTextCodecPass(t, `"float"`, math.NaN(), []byte("null")) + testTextDecodePass(t, `"float"`, math.Copysign(0, -1), []byte("-0")) +} diff --git a/fork/goavro/fuzz_test.go b/fork/goavro/fuzz_test.go new file mode 100644 index 0000000..272d491 --- /dev/null +++ b/fork/goavro/fuzz_test.go @@ -0,0 +1,416 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "bytes" + "strings" + "testing" +) + +func TestCrashers_OCFReader(t *testing.T) { + var crashers = map[string]string{ + "scan: negative block sizes": "Obj\x01\x04\x16avro.schema\x96\x05{" + + "\"type\":\"record\",\"nam" + + "e\":\"c0000000\",\"00000" + + "0000\":\"00000000000\"," + + "\"fields\":[{\"name\":\"u" + + "0000000\",\"type\":\"str" + + "ing\",\"000\":\"00000000" + + "0000\"},{\"name\":\"c000" + + "000\",\"type\":\"string\"" + + ",\"000\":\"000000000000" + + "00000000000000000000" + + "0\"},{\"name\":\"t000000" + + "00\",\"type\":\"long\",\"0" + + "00\":\"000000000000000" + + "0000000000000000\"}]," + + "\"0000\":\"000000000000" + + "00000000000000000000" + + "00000000\"}\x14000000000" + + "0\b0000\x000000000000000" + + "0000\xd90", + } + + for testName, f := range crashers { + ensureNoPanic(t, testName, func() { + _, _ = NewOCFReader(strings.NewReader(f)) // ensure does not panic + }) + } +} + +func TestCrashers_OCF_e2e(t *testing.T) { + var crashers = map[string]string{ + "map: initialSize overflow": "Obj\x01\x04\x14avro.codec\bnul" + + "l\x16avro.schema\xa2\x0e{\"typ" + + "e\":\"record\",\"name\":\"" + + "test_schema\",\"fields" + + "\":[{\"name\":\"string\"," + + "\"type\":\"string\",\"doc" + + "\":\"Meaningless strin" + + "g of characters\"},{\"" + + "name\":\"simple_map\",\"" + + "type\":{\"type\":\"map\"," + + "\"values\":\"int\"}},{\"n" + + "ame\":\"complex_map\",\"" + + "type\":{\"type\":\"map\"," + + "\"values\":{\"type\":\"ma" + + "p\",\"values\":\"string\"" + + "}}},{\"name\":\"union_s" + + "tring_null\",\"type\":[" + + "\"null\",\"string\"]},{\"" + + "name\":\"union_int_lon" + + "g_null\",\"type\":[\"int" + + "\",\"long\",\"null\"]},{\"" + + "name\":\"union_float_d" + + "ouble\",\"type\":[\"floa" + + "t\",\"double\"]},{\"name" + + "\":\"fixed3\",\"type\":{\"" + + "type\":\"fixed\",\"name\"" + + ":\"fixed3\",\"size\":3}}" + + ",{\"name\":\"fixed2\",\"t" + + "ype\":{\"type\":\"fixed\"" + + ",\"name\":\"fixed2\",\"si" + + "ze\":2}},{\"name\":\"enu" + + "m\",\"type\":{\"type\":\"e" + + "num\",\"name\":\"Suit\",\"" + + "symbols\":[\"SPADES\",\"" + + "HEARTS\",\"DIAMONDS\",\"" + + "CLUBS\"]}},{\"name\":\"r" + + "ecord\",\"type\":{\"type" + + "\":\"record\",\"name\":\"r" + + "ecord\",\"fields\":[{\"n" + + "ame\":\"value_field\",\"" + + "type\":\"string\"}],\"al" + + "iases\":[\"Reco\x9adAlias" + + "\"]}},{\"name\":\"array_" + + "of_boolean\",\"type\":{" + + "\"type\":\"array\",\"item" + + "s\":\"boolean\"}},{\"nam" + + "e\":\"bytes\",\"type\":\"b" + + "ytes\"}]}\x00\xfeJ\x17\u007f\xb4r\x11\x0e\x96&\x0e" + + "\xda<\xed\x86\xf6\x06\xfa\x05(OMG SPARK I" + + "S AWESOME\x04\x06abc\x02\x06bcd\x0e" + + "\x00\x02\x06key\x03\x80\x00\x02d\x02a\x02b\x00\x00\x01\x00\x00" + + "\x00\x00\x00\x04\x00\xdb\x0fI@\x02\x03\x04\x11\x12\x00\xb6\x01Two" + + " things are infinite" + + ": the universe and h" + + "uman stupidity; and " + + "I'm not sure about " + + "universe.\x06\x01\x00\x00\x00\x06ABCT\x00e" + + "rran is IMBA!\x04\x06qqq\x84\x01" + + "\x06mmm\x00\x00\x02\x06key\x04\x023\x024\x021\x02K" + + "��~\x02\x84\x01\x02`\xaa\xaa\xaa\xaa\xaa\x1a@\a" + + "\a\a\x01\x02\x06\x9e\x01Life did no\xef\xbf" + + "\xbd\ttend to make us pe" + + "rfect. Whoever is pe" + + "rfect `elongs in a m" + + "useum.\x00\x00$The cake is" + + " a LIE!\x00\x02\x06key\x00\x00\x00\x04\x02\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x11\"\t\x10\x90\x04\x16TEST_ST" + + "R123\x00\x04\x00\x02S\xfeJ\x17\u007f\xb4r\x11\x0e\x96&\x0e" + + "\xda<\xed\x86\xf6", + "map: initialSize overflow-2": "Obj\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\x010", + "array: initialSize overflow": "Obj\x01\x04\x14avro.codec\bnul" + + "l\x16avro.schema\xa2\x0e{\"typ" + + "e\":\"record\",\"name\":\"" + + "test_schema\",\"fields" + + "\":[{\"name\":\"string\"," + + "\"type\":\"string\",\"doc" + + "\":\"Meaningless strin" + + "g of characters\"},{\"" + + "name\":\"simple_map\",\"" + + "type\":{\"type\":\"map\"," + + "\"values\":\"int\"}},{\"n" + + "ame\":\"complex_map\",\"" + + "type\":{\"type\":\"map\"," + + "\"values\":{\"type\":\"ma" + + "p\",\"values\":\"string\"" + + "}}},{\"name\":\"union_s" + + "tring_null\",\"type\":[" + + "\"null\",\"string\"]},{\"" + + "name\":\"union_int_lon" + + "g_null\",\"type\":[\"int" + + "\",\"long\",\"null\"]},{\"" + + "name\":\"union_float_d" + + "ouble\",\"type\":[\"floa" + + "t\",\"double\"]},{\"name" + + "\":\"fixed3\",\"type\":{\"" + + "type\":\"fixed\",\"name\"" + + ":\"fixed3\",\"size\":3}}" + + ",{\"name\":\"fixed2\",\"t" + + "ype\":{\"type\":\"fixed\"" + + ",\"name\":\"fixed2\",\"si" + + "ze\":2}},{\"name\":\"enu" + + "m\",\"type\":{\"type\":\"e" + + "num\",\"name\":\"Suit\",\"" + + "symbols\":[\"SPADES\",\"" + + "HEARTS\",\"DIAMONDS\",\"" + + "CLUBS\"]}},{\"name\":\"r" + + "ecord\",\"type\":{\"type" + + "\":\"record\",\"name\":\"r" + + "ecord\",\"fields\":[{\"n" + + "ame\":\"value_field\",\"" + + "type\":\"string\"}],\"al" + + "iases\":[\"Reco\x9adAlias" + + "\"]}},{\"name\":\"array_" + + "of_boolean\",\"type\":{" + + "\"type\":\"array\",\"item" + + "s\":\"boolean\"}},{\"nam" + + "e\":\"bytes\",\"type\":\"b" + + "ytes\"}]}\x00\xfeJ\x17\u007f\xb4r\x11\x0e\x96&\x0e" + + "\xda<\xed\x86\xf6\x06\xfa\x05(OMG SPARK I" + + "S AWESOME\x04\x06abc\x02\x06bcd\x0e" + + "\x00\x02\x06key\x03\x80\x00\x02d\x02a\x02b\x00\x00\x01\x00\x00" + + "\x00\x00\x00\x04\x00\xdb\x0fI@\x02\x03\x04\x11\x12\x00\xb6\x01Two" + + " things are infinite" + + ": the universe and h" + + "uman stupidity; and " + + "I'm not sure about u" + + "n������\xef" + + "\xbf\xbd�is IMBA!\x04\x06qqq\x84\x01" + + "\x06mmm\x00\x00\x02\x06key\x04\x023\x024\x021\x022" + + "\x00\x00\x02\x06123\x02\x84\x01\x02`\xaa\xaa\xaa\xaa\xaa\x1a@\a" + + "\a\a\x01\x02\x06\x9e\x01Life did no\xef\xbf" + + "\xbd\ttend to make us pe" + + "rfect. Whoever is pe" + + "rfect `elongs in a m" + + "useum.\x00\x00$The cake is" + + " a LIE!\x00\x02\x06key\x00\x00\x00\x04\x02\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x11\"\t\x10\x90\x04\x16TEST_ST" + + "R123\x00\x04\x00\x02S\xfeJ\x17\u007f\xb4r\x11\x0e\x96&\x0e" + + "\xda<\xed\x86\xf6", + "scan: blockSize overflow": "Obj\x01\x04\x14avro.codec\fsna" + + "ppy\x16avro.schema\xf2\x05{\"t" + + "ype\":\"record\",\"name\"" + + ":\"twitter_schema\",\"n" + + "amespace\":\"com.migun" + + "o.avro\",\"fields\":[{\"" + + "name\":\"username\",\"ty" + + "pe\":\"string\",\"doc\":\"" + + "Name of the user acc" + + "ount on Twitter.com\"" + + "},{\"name\":\"tweet\",\"t" + + "ype\":\"string\",\"doc\":" + + "\"The content of the " + + "user's Twitter messa" + + "ge\"},{\"name\":\"timest" + + "amp\",\"type\":\"long\",\"" + + "doc\":\"Unix epoch tim" + + "e in milliseconds\"}]" + + ",\"doc:\":\"A basic sch" + + "ema for storing Twit" + + "ter messages\"}\x00.\xe2\xf3\xee\x96" + + "\nw2\xc3*5\\\x951\xa4\xae~\xa2\x8f\xdc\xf8\xa3H", + "fixed: size conversion: positive float64 -> negative int": "Obj\x01\x04\x14avro.codec\x0edef" + + "late\x16avro.schema\xa2\x0e{\"" + + "type\":\"record\",\"name" + + "\":\"test_schema\",\"fie" + + "lds\":[{\"name\":\"strin" + + "g\",\"type\":\"string\",\"" + + "doc\":\"Meaningless st" + + "ring of characters\"}" + + ",{\"name\":\"simple_map" + + "\",\"type\":{\"type\":\"ma" + + "p\",\"values\":\"int\"}}," + + "{\"name\":\"complex_map" + + "\",\"type\":{\"type\":\"ma" + + "p\",\"values\":{\"type\":" + + "\"map\",\"values\":\"stri" + + "ng\"}}},{\"name\":\"unio" + + "n_string_null\",\"type" + + "\":[\"null\",\"string\"]}" + + ",{\"name\":\"union_int_" + + "long_null\",\"type\":[\"" + + "int\",\"long\",\"null\"]}" + + ",{\"name\":\"union_floa" + + "t_double\",\"type\":[\"f" + + "loat\",\"double\"]},{\"n" + + "ame\":\"fixed3\",\"type\"" + + ":{\"type\":\"fixed\",\"na" + + "me\":\"fixed3\",\"size\":" + + "3}},{\"name\":\"fixed2\"" + + ",\"type\":{\"type\":\"fix" + + "ed\",\"name\":\"fixed2\"," + + "\"size\":6938893903907" + + "22837764769792556762" + + "6953125,\"name\":\"Suit" + + "\",\"symbols\":[\"SPADES" + + "\",\"HEARTS\",\"DIAMONDS" + + "\",\"CLUBS\"]}},{\"name\"" + + ":\"record\",\"type\":{\"t" + + "ype\":\"record\",\"name\"" + + ":\"record\",\"fields\":[" + + "{\"name\":\"value_field" + + "\",\"type\":\"string\"}]," + + "\"aliases\":[\"RecordAl" + + "ias\"]}},{\"name\":\"arr" + + "ay_of_boolean\",\"type" + + "\":{\"type\":\"array\",\"i" + + "tems\":\"boolean\"}},{\"" + + "name\":\"bytes\",\"type\"" + + ":\"bytes\"}]}\x00\x90\xfb\x1eO%\x06%B" + + "\x03\x00s\x0f(\x89\x02\a\x06\x82&\x1d\x97K\xa8\x9dg\x15\x86\xf7" + + "9l\x8f\xc74\x84\x10B\x88\xa1\x04\x04\x11\x1d8\x14g\xa9\"8" + + "R\xe7N\x84\xef~[\xdf\xfd>\xac\xa0\x93Nl5\x95B\xb5\xa8" + + "\x88U\x10\x8a\x04\xa1H\xb5\xb6b\x89ZL\xa5\x88x\x03+T\x87" + + "\x8a\x11\xec@p\x1d\x87g\xef\xff\xfc\xff\xf7\xaf\xf5\xbe\xcf\xfb\xee+" + + "~4\x10#]:Z\xfa\xbd\xbbO\xdc=\xbb=\x1cp#\xa6\x02" + + "\xae\xa73\xf0\xc2\xd3\x0f\xff\xf6\x9eۓ/jK\xe4\x1dx\xb5}" + + "\xbe\xf1\xab?>" + + "F{z[\xcdY\x14w$\xec \x17U\x97]\x16\xc5\xcf\xc3\r" + + "_j\xdeV\x05\xd6\xe7\xd9ᛧk\x04\xaaX\xf76ڡ\f" + + "\xaf1\x10N\x94\xe4֮E\x88Rswi\x19\x95s\x1a\x06\xa5" + + "\xab\xb0{a$E]!\x15\xba<[\x9bQ\xed\xc6ܤ$\x11" + + "\xd9\fi$\xcdx\x9c\xa6\x89-\xbc\xe1m\xa4\xdcz0\x91t%" + + "\x8b\xd9C딽YMFݕ\xee\xcd\bG|\x9c\xccX\x1a" + + "\xf2\xbaI\x197V\x96\xd8i\xcc\u007f<ѪLKt\xcf|\xd9" + + "U\x05\xe9:I\xaf5\xad\xc6U!\x16Tּe\xab\x8b\xech" + + "\x8c\xb4N\xbb\x82j\x9a\U000f16a2I\x98\x96U\xb1l\x8e\x81\xe3" + + "\x91\xaa0\xba#\x03\xce\xe3.rf\xa8D\x12\br\xa4ڬ\xe5" + + "\xf2\xe6\xdaմ\x99\xe2\xe8kݶ\xb9\x84\xb5ea>\x88\xd8\xe6" + + "\xe1l\xf4tv%\x17\xef\xa8\xf6\xc7=\x0e\xd7,\xde\xd1\xe7\\\xd9" + + "MS\r\x1f\xa9Ԩ\xb6?\xe6~8\x9c^\xd3\x1bD\xc2\xe1\xf2" + + "ӧ\xfe\xf1\xc6+\xf7^{\xe5\xbb\xee\xf0\xf2/\x9f\xbb\xf3ٯ" + + "\xfc\xe8\x1b\xe3\xecH\xec\xd9\xc9\xc9\xc9\xe1\xfc\v\xcf\u007f❣\xab" + + "\xe7g\xf8\x90\xb7\xfe\xf5\xcf\xfb'7\xadć\xf6\xb4D\x9a\xe5\xd5" + + "\xef\xdf\u007f\xf6\xe4F\xee\x13\xd4\x02\xb9\x9a\xff\xebK\u007fzx\xf9\n" + + "d¶\xe5o\xff\xe4\x9e>\x9c\xdfҶw\xbb\xd4\xecc\xcc~" + + "\xfc\xfdI\xa7\x15\x88\b\xd3\x105\x95\x94c\xaf\x0eL\xe8,\xea\"" + + ",\x87\x94V\x91\xbay\xb6\xb3\x8d\xb1z.Zu4\xb5\xc5}r" + + "R\x93\xb1q>A\xdbA\xbdl(f\xd6w\xa9[T]\x92\x1d" + + "\xa6\x8fX<\xb1Z҉\x1b\x1e\xeb\nU\x9b\xd74\xafֱD" + + "H[\\\x87\xa0\x19\xdb;,w8\xed\x87\xf3\x046\x1c\x1fE)" + + "\x051d\x11i\xd4(n\xf2\x94S\x18\x8aɲ\xf8uR\xacK" + + "\x8c̦\x8eU\x1c.i\xbb\x05\x1cp\x84/\xbf\xf5\xbb\xfbw\xef" + + "?x\xf3\xe1\xc9\xe1\xeb\x1f\xfb\xe0c\x8f}\xe0\xa3\x1foǫ|" + + "\x1aU\xa9!\x97\x0e8\xbe\xc3\xe1\xfa\v/\xfe\xf6\x87\x9f\xb9\xf9\xe9" + + "\xe7\xee\xfd<\x90\xdd͊R&C,\xeb$\x89\x11l\t*\xcf" + + "\xeew\xad\xbcj\xa1k\xf7<\x13CV\x81\x14\x8bV@}?\u007f" + + "\x14J\x90\xad\xb7>[\xb6:\xbe\xf8\xe4\xcf^?\xb9E)\x10t" + + "\x8av[/\xf3\xfc\xab\x8f\xbft\xf2\xf9\x13[T\xe4\xa2*\x87j" + + "\xcfd\x1b\xb0\x8d\x9a\xccRw~\x84\xcaJ\x94\x1dŶ)\x17\xd6" + + "\xa0s\x84MΓ\x19\xba\xdc\xc3\xe5\xe0\n\xbc\xf9\xfa\x8f\xbf\x86\x1a" + + "\x12B\xa1r\x9ey\xe6\xdf\u007f\u007f\xf7\xe1\xfcp\xbcbkk3\xaf" + + "/\xbd\x8f\xe4\x1d3I}4\xcbJ\x92\xf8:\xccJ_@\xa9m" + + "\xdcf\xe07\U000dc520/^ĵ\x9a\x80\f>\xebڭ\xf6" + + "J\x18s{Q>\u009e=\x0e\x1dL%=\nt\xac\x97\xb2\xe3" + + "\x1eX\x1a\xb2\xe2\xd7(\xef4\xc1\xe7\xe5\xe8f\x9b\xaf\x8a#\xc5]" + + "\x90@\xf5,n[\x1aѶ%+\xbf\xc0̵\xe2\xacj\xd1T" + + "\xb7\xe2\t\xa6\x8f\xb1\xc7\xdaFm\x8cWe2ߑ\xec\xca\xdb\xd4" + + "\x8dТ&ϛ\xc6]\xd1\xd8\xd5\xe2\xc5\x1d5\x99\x03哢" + + "\x8b\xa3\"\x12mhhp\xb2\xd5ac\xaa]\xc5\\\xecFY\xd2" + + "\\\xf0DEr\x1c\xf8J*\x89\xac\x8c\xf3\xbeg\nx\xd6Zq" + + "\xabi\x0f\xc9s!E\x90I\nE\xd5\xf7&\xa8\xb5\xa5\x00\x99\\" + + "1$\x95\x03\xd9v\x0f\x99\xd20\x96\x1ec\xf5\xa9Bɣm\x8f" + + "7\a\x03\\\t&ypF7i\xbc\v\xb58'i\xed\x14\x04" + + "*S\xf3\xa9z\x8dMVE\x8av\"w^\x96\x12V\x80\x8a\x1e" + + "\x8f\x88\xfa\xa5\xc8\xe1\xad\x13mr\x03\xcc\n\x13ᬤ\x8e\x85!" + + "\r\r\x05\x96Z\xdbh\xb2MF\xd0[\x8fiM\xa1\x95(o\xd8" + + "^\x8eۤ\xc4\xcaVHɍ\x12\x938\xbe\xa0k\xa9\x9a1\x11" + + "'m\x9d\xa0F\x94\xe8XZ\xc98njj\xa5\xf5\x98\xe8\x14\xba" + + "\x05\xa1UU\x11\x97l\x93&\xacY\xcc\x11B\xaa3y\xc1ސ" + + "\x9bp\xa98\x1f\x17\xa2?\xf2\x9d\xcb.)$\x06D\x87\x8b\xa0\x01" + + "ӕ\xb7\xd9L\x9fC\x17\xccM%\xd4\bLv\x1f\xad@\n\xe3" + + "9\xb5\x1a\xb4)i\xf6\xa4\x90CF\xa0\"l\xc0\x94\x84\xd9f\x99" + + "\xb2|l\v\x16e\x01Zp\xe2\x06\xe9aC\x15\xdeɴ\r\x89" + + "k\xe1\x149\x84\x9e\xe5\xd0\xd4V\x88(ֆSǜ\xa0\x83\xa9" + + "a0{X[qX\xcf\xdd\x14\t\x9d\a[N\xa16\xb50U" + + "\xa9\xba\x06\x92\xb5+K\xcb\x0e\r\xff\x12\xa3\xc4U\xac\u0381\xa9D" + + " \x1a\x87\xdc\xf7ɚ\xa0T/\xcb\bc\xccT\xcaY)-_" + + "\xdbV\xbd\xfd\xb4i4\xbc\xd0\r\xbeZ@B\a\vqr\x89d" + + "\xd1*64\xa6\x1cuՅ\xccJ\x1d\xa2\xb5\xb1F\xe7{\x8c\"" + + "\x91\xec5\n\x88K\xa8nr\x9ey\xef\xbew\xb0\x9b\x05 \t\xdd" + + "\xbcCA\x05\xa5\xac\x88\x02\xef!ɍ\x91\xb13\xa3\xae//B" + + "\b\xac\xf2\xc4\xf2\xaeR($\x8d\x9c{T<\xa86\xcc\v@\x8f" + + "\x06҅oy3\xd4\\7\xcaB\xb0\x9d\x13Fg\xf2\x937-" + + "\x97\n\x12\x93\x105d\xdd\xee\"\x19@\x9aY\xbd2\x0e\xc0\xc4\xc5" + + "\xa8\x0fzb^\x13L\u007f\f\x9f\xb6\xa8fjE\xe6\x02>\x8d\xd7" + + "ͽ\xee.-\u0099Wc\x99\xa2\xd1*\x99\xb6Ѧ\xac$\x88" + + "\x8a\x99T\x94\x99\x06P\xab\x05C\xc5X\xccJN\fzp\xa4\xc6" + + "}'9\xf1\x1e\x97\xa2\xc2\x10\xe8\xbc\xf35\xf3\u0604s\x99b`" + + "\x94+̧\xd8W\x11\xe8\xbe\x1dr\xa3\xca\v\xac:\x8393\x16" + + "\x93\x11\x9cs\r\xf7ra\xe42GV\b\xcdRݖ\x11'\xe6" + + "|\xb1F\x916\xa1\x10\x8f\xfb\xf7\xc9`5Bs\xb6=\xb7\x16A" + + "RG\x84O\x1e!\xed\x12_\xcbi\x96UfX\xa4\xaa^\xd8Z" + + "B\xe3\xdcAх3\x1a\xca\x0e\xae\x8d9\x18\x1e\x86)\xe1\x01\xab" + + "\xc1,x\a+h\xa6\xb3S\x1d7\xbaN\x8d\vZ\x1a\x14A\xc5" + + "q7\"\x91\xfa\x1b\xabJ\xc7\xf4\xe7\xd9\x06\x9f\x94\xf2\xd4Թ\xc4" + + "\xb6\xab\x04\xac\x17\x02\xef\xb8\xc3\xde\x18\x90\xdbM\x17\xb3j\xa2\xba\xff" + + "\v\x80\x8b8V\xa8\xce\xd5.\xd3D\x82\xf6b\x03\xfa\xa5\xc3d\xb3" + + "\x106X\x91{\xbb\xac8~\xe0\x15\xce\x18\x10jDg\x9b*F" + + "h\xc3V\xe1\t2pV\xba}߹F\x83%f\xba\xa5s\xea" + + "8\xf9\xc58>\xb8l\xa60\x06\x88\x1eVC\xc5\xe640\xab\x98" + + "\u09a9\x99\x90\x15\xa0\xb0y\x18ΆD\x1e\xf6\x94u\xda|N" + + "<+\x86\x1et\xa0\x9a\xd4\xed\x05\xabsD\x14\x06%\x94v2\xbc" + + "\xcf2g\xea\x16\x06(vK+pa\x9a\xed\xb6cپ4X" + + "\xb8\xb8@ad&\x91?i1b\x86]\x94\xf5\xc0\x1a\xa9!-" + + "\xb43\b\x94\x11\x84\vʹ\x8c\x95\r\xf3'\x99^\xb9\x1d\x80\xed" + + "'\xcdޖ\a\x13/\x8a\x96p3\xa6\x84\xcd̊\x14\xb3\x17\xaa" + + "ٞQ\xe5\x80U\xb0\xe6ū\xaf\r\x92\x95c\xb8с \xeb" + + "\xe6t\xaeT\xc4DG\xc4\x12$\xe8T\x83\x10dt\xbe\xf8Ve" + + "\xbai\b\x820\x83\x1d \x91\x80\xee\xc9\x16\xf914\n?\xe2\x00" + + "\xb4\x1e\x18\xf3\x8eZ\x02t\xf8fUČ\xa1\xa0\x86\x94\x91\xb3\xcd" + + "td\xb6.\x89\uf06e\x8e\x1a\x99\x17\xa8\x8c\xf8z8\x15_\x86" + + "\xc0J ;.\r!1\xb0i\n\xe8yH\xf06Av\x9c\x02" + + "*R\x00Ŕ\xf4d\xa3D\x9b\xf4 ;\x8a\x9c\xfb&Y\xa9\xa1" + + "\xf8\xe00\x8a\xab\x05<\x9e\xc3\xe8\xc3y\xf2:5q\x80\xa46\v" + + "\xb3\xd4U\n\xc9_\xb4`\xca45<:\xa7\xbd\xc5lĘ\x14" + + "\x8ee\x0e\bn\x9c\x8c\xf4}HTD\n\xb8\xfaX\xf3(\x03\xcf" + + "\xe5\xb2\xf5\xc3b1\x97\x805\xdd#([\xf0\f\x96&\xb8\x16$" + + "J\xb4<\a\xf0\xe9\x1c 0 { + tb.Fatalf("BinaryDecode ought to have returned nil buffer: %v", buf) + } + nativeData[i] = nativeDatum + } + return nativeData +} + +func nativeFromTextUsingV2(tb testing.TB, codec *Codec, textData [][]byte) []interface{} { + tb.Helper() + nativeData := make([]interface{}, len(textData)) + for i, textDatum := range textData { + nativeDatum, buf, err := codec.NativeFromTextual(textDatum) + if err != nil { + tb.Fatal(err) + } + if len(buf) > 0 { + tb.Fatalf("TextDecode ought to have returned nil buffer: %v", buf) + } + nativeData[i] = nativeDatum + } + return nativeData +} diff --git a/fork/goavro/helpers_test.go b/fork/goavro/helpers_test.go new file mode 100644 index 0000000..fd93853 --- /dev/null +++ b/fork/goavro/helpers_test.go @@ -0,0 +1,86 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "io" + "runtime" + "sync" + "testing" +) + +func benchmarkLowAndHigh(b *testing.B, callback func()) { + b.Helper() + // Run test case in parallel at relative low concurrency + b.Run("Low", func(b *testing.B) { + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + callback() + } + }) + }) + + // Run test case in parallel at relative high concurrency + b.Run("High", func(b *testing.B) { + concurrency := runtime.NumCPU() * 1000 + wg := new(sync.WaitGroup) + wg.Add(concurrency) + b.ResetTimer() + + for c := 0; c < concurrency; c++ { + go func() { + defer wg.Done() + + for n := 0; n < b.N; n++ { + callback() + } + }() + } + + wg.Wait() + }) +} + +// ShortWriter returns a structure that wraps an io.Writer, but returns +// io.ErrShortWrite when the number of bytes to write exceeds a preset limit. +// +// Copied with author's permission from https://github.com/karrick/gorill. +// +// bb := NopCloseBuffer() +// sw := ShortWriter(bb, 16) +// +// n, err := sw.Write([]byte("short write")) +// // n == 11, err == nil +// +// n, err := sw.Write([]byte("a somewhat longer write")) +// // n == 16, err == io.ErrShortWrite +func ShortWriter(w io.Writer, max int) io.Writer { + return shortWriter{Writer: w, max: max} +} + +func (s shortWriter) Write(data []byte) (int, error) { + var short bool + index := len(data) + if index > s.max { + index = s.max + short = true + } + n, err := s.Writer.Write(data[:index]) + if short { + return n, io.ErrShortWrite + } + return n, err +} + +type shortWriter struct { + io.Writer + max int +} diff --git a/fork/goavro/integer.go b/fork/goavro/integer.go new file mode 100644 index 0000000..320615f --- /dev/null +++ b/fork/goavro/integer.go @@ -0,0 +1,262 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "fmt" + "io" + "reflect" + "strconv" +) + +const ( + intDownShift = uint32(31) + intFlag = byte(128) + intMask = byte(127) + longDownShift = uint32(63) +) + +//////////////////////////////////////// +// Binary Decode +//////////////////////////////////////// + +func intNativeFromBinary(buf []byte) (interface{}, []byte, error) { + var offset, value int + var shift uint + for offset = 0; offset < len(buf); offset++ { + b := buf[offset] + value |= int(b&intMask) << shift + if b&intFlag == 0 { + return (int32(value>>1) ^ -int32(value&1)), buf[offset+1:], nil + } + shift += 7 + } + return nil, nil, io.ErrShortBuffer +} + +func longNativeFromBinary(buf []byte) (interface{}, []byte, error) { + var offset int + var value uint64 + var shift uint + for offset = 0; offset < len(buf); offset++ { + b := buf[offset] + value |= uint64(b&intMask) << shift + if b&intFlag == 0 { + return (int64(value>>1) ^ -int64(value&1)), buf[offset+1:], nil + } + shift += 7 + } + return nil, nil, io.ErrShortBuffer +} + +//////////////////////////////////////// +// Binary Encode +//////////////////////////////////////// + +func intBinaryFromNative(buf []byte, datum interface{}) ([]byte, error) { + var value int32 + switch v := datum.(type) { + case int32: + value = v + case int: + if value = int32(v); int(value) != v { + return nil, fmt.Errorf("cannot encode binary int: provided Go int would lose precision: %d", v) + } + case uint32: + if value = int32(v); value < 0 { + return nil, fmt.Errorf("cannot encode binary long: provided Go uint32 would lose precision: %d", v) + } + case int64: + if value = int32(v); int64(value) != v { + return nil, fmt.Errorf("cannot encode binary int: provided Go int64 would lose precision: %d", v) + } + case uint64: + if value = int32(v); value < 0 { + return nil, fmt.Errorf("cannot encode binary long: provided Go uint32 would lose precision: %d", v) + } + case float64: + if value = int32(v); float64(value) != v { + return nil, fmt.Errorf("cannot encode binary int: provided Go float64 would lose precision: %f", v) + } + case float32: + if value = int32(v); float32(value) != v { + return nil, fmt.Errorf("cannot encode binary int: provided Go float32 would lose precision: %f", v) + } + case string: + var err error + var tmp int64 + // it will fail if the string value does not match the size of an int32 ;) + if tmp, err = strconv.ParseInt(v, 10, 32); err != nil { + return nil, fmt.Errorf("cannot encode binary long: the provided string cannot be converted into int32 received: %s, %w", v, err) + } + value = int32(tmp) + default: + valueOf := reflect.ValueOf(v) + switch valueOf.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, + reflect.Int32, reflect.Int64: + if value = int32(valueOf.Int()); valueOf.Int() != int64(value) { + return nil, fmt.Errorf("cannot encode binary int: provided Go %s would lose precision: %d", reflect.TypeOf(v), v) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, + reflect.Uint32, reflect.Uint64: + if value = int32(valueOf.Uint()); value < 0 { + return nil, fmt.Errorf("cannot encode binary int: provided Go %s would lose precision: %d", reflect.TypeOf(v), v) + } + case reflect.Float32, reflect.Float64: + if value = int32(valueOf.Float()); valueOf.Float() != float64(value) { + return nil, fmt.Errorf("cannot encode binary int: provided Go %s would lose precision: %d", reflect.TypeOf(v), v) + } + default: + return nil, fmt.Errorf("cannot encode binary int: expected: Go numeric; received: %T", datum) + } + } + encoded := uint64((uint32(value) << 1) ^ uint32(value>>intDownShift)) + return integerBinaryEncoder(buf, encoded) +} + +func longBinaryFromNative(buf []byte, datum interface{}) ([]byte, error) { + var value int64 + switch v := datum.(type) { + case int64: + value = v + case int: + value = int64(v) + case int32: + value = int64(v) + case uint32: + value = int64(v) + case uint64: + value = int64(v) + case float64: + if value = int64(v); float64(value) != v { + return nil, fmt.Errorf("cannot encode binary long: provided Go float64 would lose precision: %f", v) + } + case float32: + if value = int64(v); float32(value) != v { + return nil, fmt.Errorf("cannot encode binary long: provided Go float32 would lose precision: %f", v) + } + case string: + var err error + if value, err = strconv.ParseInt(v, 10, 64); err != nil { + var tmp uint64 + if tmp, err = strconv.ParseUint(v, 10, 64); err != nil { + return nil, fmt.Errorf("cannot encode binary long: the provided string cannot be converted into int64 received: %s, %w", v, err) + } else { + value = int64(tmp) + } + } + default: + valueOf := reflect.ValueOf(v) + switch valueOf.Kind() { + case reflect.Int, reflect.Int32, reflect.Int64: + value = valueOf.Int() + case reflect.Uint, reflect.Uint32, reflect.Uint64: + if value = int64(valueOf.Uint()); value < 0 { + return nil, fmt.Errorf("cannot encode binary long: provided Go %s would lose precision: %d", reflect.TypeOf(v), v) + } + case reflect.Float32, reflect.Float64: + if value = int64(valueOf.Float()); valueOf.Float() != float64(value) { + return nil, fmt.Errorf("cannot encode binary long: provided Go %s would lose precision: %d", reflect.TypeOf(v), v) + } + default: + return nil, fmt.Errorf("cannot encode binary long: expected: Go numeric; received: %T", datum) + } + } + encoded := (uint64(value) << 1) ^ uint64(value>>longDownShift) + return integerBinaryEncoder(buf, encoded) +} + +func integerBinaryEncoder(buf []byte, encoded uint64) ([]byte, error) { + // used by both intBinaryEncoder and longBinaryEncoder + if encoded == 0 { + return append(buf, 0), nil + } + for encoded > 0 { + b := byte(encoded) & intMask + encoded = encoded >> 7 + if encoded != 0 { + b |= intFlag // set high bit; we have more bytes + } + buf = append(buf, b) + } + return buf, nil +} + +//////////////////////////////////////// +// Text Decode +//////////////////////////////////////// + +func longNativeFromTextual(buf []byte) (interface{}, []byte, error) { + return integerTextDecoder(buf, 64) +} + +func intNativeFromTextual(buf []byte) (interface{}, []byte, error) { + return integerTextDecoder(buf, 32) +} + +func integerTextDecoder(buf []byte, bitSize int) (interface{}, []byte, error) { + index, err := numberLength(buf, false) // NOTE: floatAllowed = false + if err != nil { + return nil, nil, err + } + datum, err := strconv.ParseInt(string(buf[:index]), 10, bitSize) + if err != nil { + return nil, nil, err + } + if bitSize == 32 { + return int32(datum), buf[index:], nil + } + return datum, buf[index:], nil +} + +//////////////////////////////////////// +// Text Encode +//////////////////////////////////////// + +func longTextualFromNative(buf []byte, datum interface{}) ([]byte, error) { + return integerTextEncoder(buf, datum, 64) +} + +func intTextualFromNative(buf []byte, datum interface{}) ([]byte, error) { + return integerTextEncoder(buf, datum, 32) +} + +func integerTextEncoder(buf []byte, datum interface{}, bitSize int) ([]byte, error) { + var someInt64 int64 + switch v := datum.(type) { + case int: + someInt64 = int64(v) + case int32: + someInt64 = int64(v) + case int64: + someInt64 = v + case float32: + if someInt64 = int64(v); float32(someInt64) != v { + if bitSize == 64 { + return nil, fmt.Errorf("cannot encode textual long: provided Go float32 would lose precision: %f", v) + } + return nil, fmt.Errorf("cannot encode textual int: provided Go float32 would lose precision: %f", v) + } + case float64: + if someInt64 = int64(v); float64(someInt64) != v { + if bitSize == 64 { + return nil, fmt.Errorf("cannot encode textual long: provided Go float64 would lose precision: %f", v) + } + return nil, fmt.Errorf("cannot encode textual int: provided Go float64 would lose precision: %f", v) + } + default: + if bitSize == 64 { + return nil, fmt.Errorf("cannot encode textual long: expected: Go numeric; received: %T", datum) + } + return nil, fmt.Errorf("cannot encode textual int: expected: Go numeric; received: %T", datum) + } + return strconv.AppendInt(buf, someInt64, 10), nil +} diff --git a/fork/goavro/integer_test.go b/fork/goavro/integer_test.go new file mode 100644 index 0000000..347223d --- /dev/null +++ b/fork/goavro/integer_test.go @@ -0,0 +1,126 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "math" + "testing" +) + +func TestSchemaPrimitiveCodecInt(t *testing.T) { + testSchemaPrimativeCodec(t, `"int"`) +} + +type Tint int +type Tint32 int32 +type Tint64 int64 +type TUint uint +type TUint32 uint32 +type TUint64 uint64 + +func TestPrimitiveIntBinary(t *testing.T) { + testBinaryEncodeFailBadDatumType(t, `"int"`, "some string") + testBinaryDecodeFailShortBuffer(t, `"int"`, []byte{0xfd, 0xff, 0xff, 0xff}) + testBinaryCodecPass(t, `"int"`, -1, []byte{0x01}) + testBinaryCodecPass(t, `"int"`, -2147483647, []byte{0xfd, 0xff, 0xff, 0xff, 0xf}) + testBinaryCodecPass(t, `"int"`, -3, []byte{0x05}) + testBinaryCodecPass(t, `"int"`, -65, []byte("\x81\x01")) + testBinaryCodecPass(t, `"int"`, 0, []byte{0x00}) + testBinaryCodecPass(t, `"int"`, 1, []byte{0x02}) + testBinaryCodecPass(t, `"int"`, 1016, []byte("\xf0\x0f")) + testBinaryCodecPass(t, `"int"`, 1455301406, []byte{0xbc, 0x8c, 0xf1, 0xeb, 0xa}) + testBinaryCodecPass(t, `"int"`, 2147483647, []byte{0xfe, 0xff, 0xff, 0xff, 0xf}) + testBinaryCodecPass(t, `"int"`, 3, []byte("\x06")) + testBinaryCodecPass(t, `"int"`, 64, []byte("\x80\x01")) + testBinaryCodecPass(t, `"int"`, 66052, []byte("\x88\x88\x08")) + testBinaryCodecPass(t, `"int"`, 8454660, []byte("\x88\x88\x88\x08")) + testBinaryCodecPass(t, `"int"`, "8454660", []byte("\x88\x88\x88\x08")) + + // uint(32|64) support + testBinaryCodecPass(t, `"int"`, uint32(64), []byte("\x80\x01")) + testBinaryWriteReadPass(t, `"int"`, uint32(math.MaxInt32)) + testBinaryWriteReadPass(t, `"int"`, uint64(math.MaxInt32)) + testBinaryEncodeFail(t, `"int"`, uint32(math.MaxInt32+1), "uint32 would lose precision") + testBinaryEncodeFail(t, `"int"`, uint64(math.MaxInt32+1), "uint32 would lose precision") + // reflect + testBinaryCodecPass(t, `"int"`, TUint(64), []byte("\x80\x01")) + testBinaryCodecPass(t, `"int"`, TUint32(64), []byte("\x80\x01")) + testBinaryCodecPass(t, `"int"`, TUint64(64), []byte("\x80\x01")) + testBinaryEncodeFail(t, `"int"`, TUint32(math.MaxInt32+1), "goavro.TUint32 would lose precision") + testBinaryEncodeFail(t, `"int"`, TUint64(math.MaxInt32+1), "goavro.TUint64 would lose precision") + testBinaryCodecPass(t, `"int"`, Tint(64), []byte("\x80\x01")) + testBinaryCodecPass(t, `"int"`, Tint32(64), []byte("\x80\x01")) + testBinaryCodecPass(t, `"int"`, Tint64(64), []byte("\x80\x01")) + + testBinaryCodecPass(t, `"long"`, TUint(64), []byte("\x80\x01")) + testBinaryCodecPass(t, `"long"`, TUint32(64), []byte("\x80\x01")) + testBinaryCodecPass(t, `"long"`, TUint64(64), []byte("\x80\x01")) + testBinaryEncodeFail(t, `"long"`, TUint64(math.MaxInt64+1), "goavro.TUint64 would lose precision") + testBinaryCodecPass(t, `"long"`, Tint(64), []byte("\x80\x01")) + testBinaryCodecPass(t, `"long"`, Tint32(64), []byte("\x80\x01")) + testBinaryCodecPass(t, `"long"`, Tint64(64), []byte("\x80\x01")) +} + +func TestPrimitiveIntText(t *testing.T) { + testTextDecodeFailShortBuffer(t, `"int"`, []byte("")) + testTextDecodeFailShortBuffer(t, `"int"`, []byte("-")) + + testTextCodecPass(t, `"int"`, -13, []byte("-13")) + testTextCodecPass(t, `"int"`, 0, []byte("0")) + testTextCodecPass(t, `"int"`, 13, []byte("13")) + testTextDecodePass(t, `"int"`, -0, []byte("-0")) + testTextEncodePass(t, `"int"`, -0, []byte("0")) // NOTE: -0 encodes as "0" +} + +func TestSchemaPrimitiveCodecLong(t *testing.T) { + testSchemaPrimativeCodec(t, `"long"`) +} + +func TestPrimitiveLongBinary(t *testing.T) { + testBinaryEncodeFailBadDatumType(t, `"long"`, "some string") + testBinaryDecodeFailShortBuffer(t, `"long"`, []byte("\xff\xff\xff\xff")) + testBinaryCodecPass(t, `"long"`, int64((1<<63)-1), []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1}) + testBinaryCodecPass(t, `"long"`, int64(-(1 << 63)), []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1}) + testBinaryCodecPass(t, `"long"`, -2147483648, []byte("\xff\xff\xff\xff\x0f")) + testBinaryCodecPass(t, `"long"`, -3, []byte("\x05")) + testBinaryCodecPass(t, `"long"`, -65, []byte("\x81\x01")) + testBinaryCodecPass(t, `"long"`, 0, []byte("\x00")) + testBinaryCodecPass(t, `"long"`, 1082196484, []byte("\x88\x88\x88\x88\x08")) + testBinaryCodecPass(t, `"long"`, int64(1359702038045356208), []byte{0xe0, 0xc2, 0x8b, 0xa1, 0x96, 0xf3, 0xd0, 0xde, 0x25}) + testBinaryCodecPass(t, `"long"`, int64(138521149956), []byte("\x88\x88\x88\x88\x88\x08")) + testBinaryCodecPass(t, `"long"`, int64(17730707194372), []byte("\x88\x88\x88\x88\x88\x88\x08")) + testBinaryCodecPass(t, `"long"`, 2147483647, []byte("\xfe\xff\xff\xff\x0f")) + testBinaryCodecPass(t, `"long"`, int64(2269530520879620), []byte("\x88\x88\x88\x88\x88\x88\x88\x08")) + testBinaryCodecPass(t, `"long"`, 3, []byte("\x06")) + testBinaryCodecPass(t, `"long"`, int64(5959107741628848600), []byte{0xb0, 0xe7, 0x8a, 0xe1, 0xe2, 0xba, 0x80, 0xb3, 0xa5, 0x1}) + testBinaryCodecPass(t, `"long"`, 64, []byte("\x80\x01")) + testBinaryCodecPass(t, `"long"`, "64", []byte("\x80\x01")) + + // https://github.com/linkedin/goavro/issues/49 + testBinaryCodecPass(t, `"long"`, int64(-5513458701470791632), []byte("\x9f\xdf\x9f\x8f\xc7\xde\xde\x83\x99\x01")) + testBinaryCodecPass(t, `"long"`, uint32(64), []byte("\x80\x01")) + testBinaryWriteReadPass(t, `"long"`, uint32(64)) + + // uint(32|64) support + testBinaryWriteReadPass(t, `"long"`, uint32(math.MaxUint32)) + testBinaryWriteReadPass(t, `"long"`, uint64(math.MaxInt64)) + // testBinaryEncodeFail(t, `"long"`, uint64(math.MaxInt64+1), "uint64 would lose precision") +} + +func TestPrimitiveLongText(t *testing.T) { + testTextDecodeFailShortBuffer(t, `"long"`, []byte("")) + testTextDecodeFailShortBuffer(t, `"long"`, []byte("-")) + + testTextCodecPass(t, `"long"`, -13, []byte("-13")) + testTextCodecPass(t, `"long"`, 0, []byte("0")) + testTextCodecPass(t, `"long"`, 13, []byte("13")) + testTextDecodePass(t, `"long"`, -0, []byte("-0")) + testTextEncodePass(t, `"long"`, -0, []byte("0")) // NOTE: -0 encodes as "0" +} diff --git a/fork/goavro/logical_type.go b/fork/goavro/logical_type.go new file mode 100644 index 0000000..1ac6b48 --- /dev/null +++ b/fork/goavro/logical_type.go @@ -0,0 +1,499 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "errors" + "fmt" + "math/big" + "regexp" + "strings" + "time" +) + +type toNativeFn func([]byte) (interface{}, []byte, error) +type fromNativeFn func([]byte, interface{}) ([]byte, error) + +var reFromPattern = make(map[string]*regexp.Regexp) + +// //////////////////////////////////////////////////////////////////////////////////////////// +// date logical type - to/from time.Time, time.UTC location +// //////////////////////////////////////////////////////////////////////////////////////////// +func nativeFromDate(fn toNativeFn) toNativeFn { + return func(bytes []byte) (interface{}, []byte, error) { + l, b, err := fn(bytes) + if err != nil { + return l, b, err + } + i, ok := l.(int32) + if !ok { + return l, b, fmt.Errorf("cannot transform to native date, expected int, received %T", l) + } + t := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC).AddDate(0, 0, int(i)).UTC() + return t, b, nil + } +} + +func dateFromNative(fn fromNativeFn) fromNativeFn { + return func(b []byte, d interface{}) ([]byte, error) { + switch val := d.(type) { + case int, int32, int64, float32, float64: + // "Language implementations may choose to represent logical types with an appropriate native type, although this is not required." + // especially permitted default values depend on the field's schema type and goavro encodes default values using the field schema + return fn(b, val) + + case time.Time: + // rephrasing the avro 1.9.2 spec a date is actually stored as the duration since unix epoch in days + // time.Unix() returns this duration in seconds and time.UnixNano() in nanoseconds + // reviewing the source code, both functions are based on the internal function unixSec() + // unixSec() returns the seconds since unix epoch as int64, whereby Unix() provides the greater range and UnixNano() the higher precision + // As a date requires a precision of days Unix() provides more then enough precision and a greater range, including the go zero time + numDays := val.Unix() / 86400 + return fn(b, numDays) + + default: + return nil, fmt.Errorf("cannot transform to binary date, expected time.Time or Go numeric, received %T", d) + } + } +} + +// //////////////////////////////////////////////////////////////////////////////////////////// +// time-millis logical type - to/from time.Time, time.UTC location +// //////////////////////////////////////////////////////////////////////////////////////////// +func nativeFromTimeMillis(fn toNativeFn) toNativeFn { + return func(bytes []byte) (interface{}, []byte, error) { + l, b, err := fn(bytes) + if err != nil { + return l, b, err + } + i, ok := l.(int32) + if !ok { + return l, b, fmt.Errorf("cannot transform to native time.Duration, expected int, received %T", l) + } + t := time.Duration(i) * time.Millisecond + return t, b, nil + } +} + +func timeMillisFromNative(fn fromNativeFn) fromNativeFn { + return func(b []byte, d interface{}) ([]byte, error) { + switch val := d.(type) { + case int, int32, int64, float32, float64: + // "Language implementations may choose to represent logical types with an appropriate native type, although this is not required." + // especially permitted default values depend on the field's schema type and goavro encodes default values using the field schema + return fn(b, val) + + case time.Duration: + duration := int32(val.Nanoseconds() / int64(time.Millisecond)) + return fn(b, duration) + + default: + return nil, fmt.Errorf("cannot transform to binary time-millis, expected time.Duration or Go numeric, received %T", d) + } + } +} + +// //////////////////////////////////////////////////////////////////////////////////////////// +// time-micros logical type - to/from time.Time, time.UTC location +// //////////////////////////////////////////////////////////////////////////////////////////// +func nativeFromTimeMicros(fn toNativeFn) toNativeFn { + return func(bytes []byte) (interface{}, []byte, error) { + l, b, err := fn(bytes) + if err != nil { + return l, b, err + } + i, ok := l.(int64) + if !ok { + return l, b, fmt.Errorf("cannot transform to native time.Duration, expected long, received %T", l) + } + t := time.Duration(i) * time.Microsecond + return t, b, nil + } +} + +func timeMicrosFromNative(fn fromNativeFn) fromNativeFn { + return func(b []byte, d interface{}) ([]byte, error) { + switch val := d.(type) { + case int, int32, int64, float32, float64: + // "Language implementations may choose to represent logical types with an appropriate native type, although this is not required." + // especially permitted default values depend on the field's schema type and goavro encodes default values using the field schema + return fn(b, val) + + case time.Duration: + duration := int32(val.Nanoseconds() / int64(time.Microsecond)) + return fn(b, duration) + + default: + return nil, fmt.Errorf("cannot transform to binary time-micros, expected time.Duration or Go numeric, received %T", d) + } + } +} + +// //////////////////////////////////////////////////////////////////////////////////////////// +// timestamp-millis logical type - to/from time.Time, time.UTC location +// //////////////////////////////////////////////////////////////////////////////////////////// +func nativeFromTimeStampMillis(fn toNativeFn) toNativeFn { + return func(bytes []byte) (interface{}, []byte, error) { + l, b, err := fn(bytes) + if err != nil { + return l, b, err + } + milliseconds, ok := l.(int64) + if !ok { + return l, b, fmt.Errorf("cannot transform native timestamp-millis, expected int64, received %T", l) + } + seconds := milliseconds / 1e3 + nanoseconds := (milliseconds - (seconds * 1e3)) * 1e6 + return time.Unix(seconds, nanoseconds).UTC(), b, nil + } +} + +func timeStampMillisFromNative(fn fromNativeFn) fromNativeFn { + return func(b []byte, d interface{}) ([]byte, error) { + switch val := d.(type) { + case int, int32, int64, float32, float64: + // "Language implementations may choose to represent logical types with an appropriate native type, although this is not required." + // especially permitted default values depend on the field's schema type and goavro encodes default values using the field schema + return fn(b, val) + + case time.Time: + // While this code performs a few more steps than seem required, it is + // written this way to allow the best time resolution without overflowing the int64 value. + return fn(b, val.Unix()*1e3+int64(val.Nanosecond()/1e6)) + + default: + return nil, fmt.Errorf("cannot transform to binary timestamp-millis, expected time.Time or Go numeric, received %T", d) + } + } +} + +// //////////////////////////////////////////////////////////////////////////////////////////// +// timestamp-micros logical type - to/from time.Time, time.UTC location +// //////////////////////////////////////////////////////////////////////////////////////////// +func nativeFromTimeStampMicros(fn toNativeFn) toNativeFn { + return func(bytes []byte) (interface{}, []byte, error) { + l, b, err := fn(bytes) + if err != nil { + return l, b, err + } + microseconds, ok := l.(int64) + if !ok { + return l, b, fmt.Errorf("cannot transform native timestamp-micros, expected int64, received %T", l) + } + // While this code performs a few more steps than seem required, it is + // written this way to allow the best time resolution on UNIX and + // Windows without overflowing the int64 value. Windows has a zero-time + // value of 1601-01-01 UTC, and the number of nanoseconds since that + // zero-time overflows 64-bit integers. + seconds := microseconds / 1e6 + nanoseconds := (microseconds - (seconds * 1e6)) * 1e3 + return time.Unix(seconds, nanoseconds).UTC(), b, nil + } +} + +func timeStampMicrosFromNative(fn fromNativeFn) fromNativeFn { + return func(b []byte, d interface{}) ([]byte, error) { + switch val := d.(type) { + case int, int32, int64, float32, float64: + // "Language implementations may choose to represent logical types with an appropriate native type, although this is not required." + // especially permitted default values depend on the field's schema type and goavro encodes default values using the field schema + return fn(b, val) + + case time.Time: + // While this code performs a few more steps than seem required, it is + // written this way to allow the best time resolution on UNIX and + // Windows without overflowing the int64 value. Windows has a zero-time + // value of 1601-01-01 UTC, and the number of nanoseconds since that + // zero-time overflows 64-bit integers. + return fn(b, val.Unix()*1e6+int64(val.Nanosecond()/1e3)) + + default: + return nil, fmt.Errorf("cannot transform to binary timestamp-micros, expected time.Time or Go numeric, received %T", d) + } + } +} + +// /////////////////////////////////////////////////////////////////////////////////////////// +// decimal logical-type - byte/fixed - to/from math/big.Rat +// two's complement algorithm taken from: +// https://groups.google.com/d/msg/golang-nuts/TV4bRVrHZUw/UcQt7S4IYlcJ by rog +// /////////////////////////////////////////////////////////////////////////////////////////// +type makeCodecFn func(st map[string]*Codec, enclosingNamespace string, schemaMap map[string]interface{}) (*Codec, error) + +func precisionAndScaleFromSchemaMap(schemaMap map[string]interface{}) (int, int, error) { + p1, ok := schemaMap["precision"] + if !ok { + return 0, 0, errors.New("cannot create decimal logical type without precision") + } + p2, ok := p1.(float64) + if !ok { + return 0, 0, fmt.Errorf("cannot create decimal logical type with wrong precision type; expected: float64; received: %T", p1) + } + p3 := int(p2) + if p3 < 1 { + return 0, 0, fmt.Errorf("cannot create decimal logical type when precision is less than one: %d", p3) + } + var s3 int // scale defaults to 0 if not set + if s1, ok := schemaMap["scale"]; ok { + s2, ok := s1.(float64) + if !ok { + return 0, 0, fmt.Errorf("cannot create decimal logical type with wrong scale type; expected: float64; received: %T", s1) + } + s3 = int(s2) + if s3 < 0 { + return 0, 0, fmt.Errorf("cannot create decimal logical type when scale is less than zero: %d", s3) + } + if s3 > p3 { + return 0, 0, fmt.Errorf("cannot create decimal logical type when scale is larger than precision: %d > %d", s3, p3) + } + } + return p3, s3, nil +} + +var one = big.NewInt(1) + +func makeDecimalBytesCodec(st map[string]*Codec, enclosingNamespace string, schemaMap map[string]interface{}) (*Codec, error) { + precision, scale, err := precisionAndScaleFromSchemaMap(schemaMap) + if err != nil { + return nil, err + } + if _, ok := schemaMap["name"]; !ok { + schemaMap["name"] = "bytes.decimal" + } + c, err := registerNewCodec(st, schemaMap, enclosingNamespace) + if err != nil { + return nil, fmt.Errorf("Bytes ought to have valid name: %s", err) + } + + // Add an additional cached codec for this "bytes.decimal" keyed also by "precision" and "scale" + decimalSearchType := fmt.Sprintf("bytes.decimal.%d.%d", precision, scale) + st[decimalSearchType] = c + + c.binaryFromNative = decimalBytesFromNative(bytesBinaryFromNative, toSignedBytes, precision, scale) + c.textualFromNative = decimalBytesFromNative(bytesTextualFromNative, toSignedBytes, precision, scale) + c.nativeFromBinary = nativeFromDecimalBytes(bytesNativeFromBinary, precision, scale) + c.nativeFromTextual = nativeFromDecimalBytes(bytesNativeFromTextual, precision, scale) + return c, nil +} + +func nativeFromDecimalBytes(fn toNativeFn, precision, scale int) toNativeFn { + return func(bytes []byte) (interface{}, []byte, error) { + d, b, err := fn(bytes) + if err != nil { + return d, b, err + } + bs, ok := d.([]byte) + if !ok { + return nil, bytes, fmt.Errorf("cannot transform to native decimal, expected []byte, received %T", d) + } + num := big.NewInt(0) + fromSignedBytes(num, bs) + denom := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(scale)), nil) + r := new(big.Rat).SetFrac(num, denom) + return r, b, nil + } +} + +func decimalBytesFromNative(fromNativeFn fromNativeFn, toBytesFn toBytesFn, precision, scale int) fromNativeFn { + return func(b []byte, d interface{}) ([]byte, error) { + var r *big.Rat + switch valueType := d.(type) { + case *big.Rat: + r = valueType + case uint64: + r = new(big.Rat).SetUint64(valueType) + default: + return nil, fmt.Errorf("cannot transform to bytes, expected *big.Rat, received %T", d) + } + // Reduce accuracy to precision by dividing and multiplying by digit length + num := big.NewInt(0).Set(r.Num()) + denom := big.NewInt(0).Set(r.Denom()) + i := new(big.Int).Mul(num, new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(scale)), nil)) + // divide that by the denominator + precnum := new(big.Int).Div(i, denom) + bout, err := toBytesFn(precnum) + if err != nil { + return nil, err + } + return fromNativeFn(b, bout) + } +} + +func makeDecimalFixedCodec(st map[string]*Codec, enclosingNamespace string, schemaMap map[string]interface{}) (*Codec, error) { + precision, scale, err := precisionAndScaleFromSchemaMap(schemaMap) + if err != nil { + return nil, err + } + if _, ok := schemaMap["name"]; !ok { + schemaMap["name"] = "fixed.decimal" + } + c, err := makeFixedCodec(st, enclosingNamespace, schemaMap) + if err != nil { + return nil, err + } + size, err := sizeFromSchemaMap(c.typeName, schemaMap) + if err != nil { + return nil, err + } + c.binaryFromNative = decimalBytesFromNative(c.binaryFromNative, toSignedFixedBytes(size), precision, scale) + c.textualFromNative = decimalBytesFromNative(c.textualFromNative, toSignedFixedBytes(size), precision, scale) + c.nativeFromBinary = nativeFromDecimalBytes(c.nativeFromBinary, precision, scale) + c.nativeFromTextual = nativeFromDecimalBytes(c.nativeFromTextual, precision, scale) + return c, nil +} + +func makeValidatedStringCodec(st map[string]*Codec, enclosingNamespace string, schemaMap map[string]interface{}) (*Codec, error) { + pattern, ok := schemaMap["pattern"] + if !ok { + return nil, errors.New("cannot create validated-string logical type without pattern") + } + + patternStr := strings.TrimSpace(pattern.(string)) + if reFromPattern[patternStr] == nil { + var ( + regexpr *regexp.Regexp + err error + ) + if regexpr, err = regexp.Compile(patternStr); err != nil { + return nil, err + } + + reFromPattern[patternStr] = regexpr + } + + if _, ok := schemaMap["name"]; !ok { + schemaMap["name"] = "string.validated-string" + } + + c, err := registerNewCodec(st, schemaMap, enclosingNamespace) + if err != nil { + return nil, err + } + + c.binaryFromNative = validatedStringBinaryFromNative(c.binaryFromNative) + c.textualFromNative = validatedStringTextualFromNative(c.textualFromNative) + c.nativeFromBinary = validatedStringNativeFromBinary(c.nativeFromBinary, patternStr) + c.nativeFromTextual = validatedStringNativeFromTextual(c.nativeFromTextual, patternStr) + return c, nil +} + +func validatedStringBinaryFromNative(fromNativeFn fromNativeFn) fromNativeFn { + return func(b []byte, d interface{}) ([]byte, error) { + return stringBinaryFromNative(b, d) + } +} + +func validatedStringTextualFromNative(fromNativeFn fromNativeFn) fromNativeFn { + return func(b []byte, d interface{}) ([]byte, error) { + return stringTextualFromNative(b, d) + } +} + +func validatedStringNativeFromBinary(fn toNativeFn, pattern string) toNativeFn { + return func(bytes []byte) (interface{}, []byte, error) { + fn, newBytes, err := stringNativeFromBinary(bytes) + if err != nil { + return nil, nil, err + } + + result := fn.(string) + if ok := reFromPattern[pattern].MatchString(result); !ok { + return nil, bytes, fmt.Errorf("cannot match input string against validation pattern: %q does not match %q", result, pattern) + } + + return fn, newBytes, nil + } +} + +func validatedStringNativeFromTextual(fn toNativeFn, pattern string) toNativeFn { + return func(bytes []byte) (interface{}, []byte, error) { + fn, newBytes, err := stringNativeFromTextual(bytes) + if err != nil { + return nil, nil, err + } + + result := fn.(string) + if ok := reFromPattern[pattern].MatchString(result); !ok { + return nil, bytes, fmt.Errorf("cannot match input string against validation pattern: %q does not match %q", result, pattern) + } + + return fn, newBytes, nil + } +} + +func padBytes(bytes []byte, fixedSize uint) []byte { + s := int(fixedSize) + padded := make([]byte, s, s) + if s >= len(bytes) { + copy(padded[s-len(bytes):], bytes) + } + return padded +} + +type toBytesFn func(n *big.Int) ([]byte, error) + +// fromSignedBytes sets the value of n to the big-endian two's complement +// value stored in the given data. If data[0]&80 != 0, the number +// is negative. If data is empty, the result will be 0. +func fromSignedBytes(n *big.Int, data []byte) { + n.SetBytes(data) + if len(data) > 0 && data[0]&0x80 > 0 { + n.Sub(n, new(big.Int).Lsh(one, uint(len(data))*8)) + } +} + +// toSignedBytes returns the big-endian two's complement +// form of n. +func toSignedBytes(n *big.Int) ([]byte, error) { + switch n.Sign() { + case 0: + return []byte{0}, nil + case 1: + b := n.Bytes() + if b[0]&0x80 > 0 { + b = append([]byte{0}, b...) + } + return b, nil + case -1: + length := uint(n.BitLen()/8+1) * 8 + b := new(big.Int).Add(n, new(big.Int).Lsh(one, length)).Bytes() + // When the most significant bit is on a byte + // boundary, we can get some extra significant + // bits, so strip them off when that happens. + if len(b) >= 2 && b[0] == 0xff && b[1]&0x80 != 0 { + b = b[1:] + } + return b, nil + } + return nil, fmt.Errorf("toSignedBytes: error big.Int.Sign() returned unexpected value") +} + +// toSignedFixedBytes returns the big-endian two's complement +// form of n for a given length of bytes. +func toSignedFixedBytes(size uint) func(*big.Int) ([]byte, error) { + return func(n *big.Int) ([]byte, error) { + switch n.Sign() { + case 0: + return []byte{0}, nil + case 1: + b := n.Bytes() + if b[0]&0x80 > 0 { + b = append([]byte{0}, b...) + } + return padBytes(b, size), nil + case -1: + length := size * 8 + b := new(big.Int).Add(n, new(big.Int).Lsh(one, length)).Bytes() + // Unlike a variable length byte length we need the extra bits to meet byte length + return b, nil + } + return nil, fmt.Errorf("toSignedBytes: error big.Int.Sign() returned unexpected value") + } +} diff --git a/fork/goavro/logical_type_test.go b/fork/goavro/logical_type_test.go new file mode 100644 index 0000000..7108af0 --- /dev/null +++ b/fork/goavro/logical_type_test.go @@ -0,0 +1,312 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "fmt" + "math" + "math/big" + "testing" + "time" +) + +const ( + precision = "precision" + scale = "scale" +) + +func TestSchemaLogicalType(t *testing.T) { + testSchemaValid(t, `{"type": "long", "logicalType": "timestamp-millis"}`) + testSchemaInvalid(t, `{"type": "bytes", "logicalType": "decimal"}`, "precision") + testSchemaInvalid(t, `{"type": "fixed", "size": 16, "logicalType": "decimal"}`, "precision") +} + +func TestStringLogicalTypeFallback(t *testing.T) { + schema := `{"type": "string", "logicalType": "this_logical_type_does_not_exist"}` + testSchemaValid(t, schema) + testBinaryCodecPass(t, schema, "test string", []byte("\x16\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67")) +} + +func TestLongLogicalTypeFallback(t *testing.T) { + schema := `{"type": "long", "logicalType": "this_logical_type_does_not_exist"}` + testSchemaValid(t, schema) + testBinaryCodecPass(t, schema, 12345, []byte("\xf2\xc0\x01")) +} + +func TestTimeStampMillisLogicalTypeEncode(t *testing.T) { + schema := `{"type": "long", "logicalType": "timestamp-millis"}` + testBinaryDecodeFail(t, schema, []byte(""), "short buffer") + t.Skip("this test is broken") + testBinaryEncodeFail(t, schema, "test", "cannot transform binary timestamp-millis, expected time.Time") + testBinaryCodecPass(t, schema, time.Date(2006, 1, 2, 15, 04, 05, 565000000, time.UTC), []byte("\xfa\x82\xac\xba\x91\x42")) +} + +func TestTimeStampMillisLogicalTypeUnionEncode(t *testing.T) { + schema := `{"type": ["null", {"type": "long", "logicalType": "timestamp-millis"}]}` + testBinaryEncodeFail(t, schema, Union("string", "test"), "cannot encode binary union: no member schema types support datum: allowed types: [null long.timestamp-millis]") + testBinaryCodecPass(t, schema, Union("long.timestamp-millis", time.Date(2006, 1, 2, 15, 04, 05, 565000000, time.UTC)), []byte("\x02\xfa\x82\xac\xba\x91\x42")) +} + +func TestTimeStampMicrosLogicalTypeEncode(t *testing.T) { + schema := `{"type": "long", "logicalType": "timestamp-micros"}` + testBinaryDecodeFail(t, schema, []byte(""), "short buffer") + t.Skip("this test is broken") + testBinaryEncodeFail(t, schema, "test", "cannot transform binary timestamp-micros, expected time.Time") + testBinaryCodecPass(t, schema, time.Date(2006, 1, 2, 15, 04, 05, 565283000, time.UTC), []byte("\xc6\x8d\xf7\xe7\xaf\xd8\x84\x04")) +} + +func TestTimeStampMicrosLogicalTypeUnionEncode(t *testing.T) { + schema := `{"type": ["null", {"type": "long", "logicalType": "timestamp-micros"}]}` + testBinaryEncodeFail(t, schema, Union("string", "test"), "cannot encode binary union: no member schema types support datum: allowed types: [null long.timestamp-micros]") + testBinaryCodecPass(t, schema, Union("long.timestamp-micros", time.Date(2006, 1, 2, 15, 04, 05, 565283000, time.UTC)), []byte("\x02\xc6\x8d\xf7\xe7\xaf\xd8\x84\x04")) +} + +func TestTimeMillisLogicalTypeEncode(t *testing.T) { + schema := `{"type": "int", "logicalType": "time-millis"}` + testBinaryDecodeFail(t, schema, []byte(""), "short buffer") + testBinaryEncodeFail(t, schema, "test", "cannot transform to binary time-millis, expected time.Duration") + testBinaryCodecPass(t, schema, 66904022*time.Millisecond, []byte("\xac\xff\xe6\x3f")) +} + +func TestTimeMillisLogicalTypeUnionEncode(t *testing.T) { + schema := `{"type": ["null", {"type": "int", "logicalType": "time-millis"}]}` + testBinaryEncodeFail(t, schema, Union("string", "test"), "cannot encode binary union: no member schema types support datum: allowed types: [null int.time-millis]") + testBinaryCodecPass(t, schema, Union("int.time-millis", 66904022*time.Millisecond), []byte("\x02\xac\xff\xe6\x3f")) +} + +func TestTimeMicrosLogicalTypeEncode(t *testing.T) { + schema := `{"type": "long", "logicalType": "time-micros"}` + testBinaryDecodeFail(t, schema, []byte(""), "short buffer") + testBinaryEncodeFail(t, schema, "test", "cannot transform to binary time-micros, expected time.Duration") + t.Skip("this test is broken") + testBinaryCodecPass(t, schema, 66904022566*time.Microsecond, []byte("\xcc\xf8\xd2\xbc\xf2\x03")) +} + +func TestTimeMicrosLogicalTypeUnionEncode(t *testing.T) { + schema := `{"type": ["null", {"type": "long", "logicalType": "time-micros"}]}` + testBinaryEncodeFail(t, schema, Union("string", "test"), "cannot encode binary union: no member schema types support datum: allowed types: [null long.time-micros]") + t.Skip("this test is broken") + testBinaryCodecPass(t, schema, Union("long.time-micros", 66904022566*time.Microsecond), []byte("\x02\xcc\xf8\xd2\xbc\xf2\x03")) +} +func TestDateLogicalTypeEncode(t *testing.T) { + schema := `{"type": "int", "logicalType": "date"}` + testBinaryDecodeFail(t, schema, []byte(""), "short buffer") + t.Skip("this test is broken") + testBinaryEncodeFail(t, schema, "test", "cannot transform to binary date, expected time.Time, received string") + testBinaryCodecPass(t, schema, time.Date(2006, 1, 2, 0, 0, 0, 0, time.UTC), []byte("\xbc\xcd\x01")) +} + +func testGoZeroTime(t *testing.T, schema string, expected []byte) { + t.Helper() + testBinaryEncodePass(t, schema, time.Time{}, expected) + + codec, err := NewCodec(schema) + if err != nil { + t.Fatal(err) + } + + value, remaining, err := codec.NativeFromBinary(expected) + if err != nil { + t.Fatalf("schema: %s; %s", schema, err) + } + + // remaining ought to be empty because there is nothing remaining to be + // decoded + if actual, expected := len(remaining), 0; actual != expected { + t.Errorf("schema: %s; Remaining; Actual: %#v; Expected: %#v", schema, actual, expected) + } + + zeroTime, ok := value.(time.Time) + if !ok { + t.Fatalf("schema: %s, NativeFromBinary: expected time.Time, got %T", schema, value) + } + + if !zeroTime.IsZero() { + t.Fatalf("schema: %s, Check: time.Time{}.IsZero(), Actual: %t, Expected: true", schema, zeroTime.IsZero()) + } +} + +func TestDateGoZero(t *testing.T) { + testGoZeroTime(t, `{"type": "int", "logicalType": "date"}`, []byte{0xf3, 0xe4, 0x57}) +} + +func TestTimeStampMillisGoZero(t *testing.T) { + testGoZeroTime(t, `{"type": "long", "logicalType": "timestamp-millis"}`, []byte{0xff, 0xdf, 0xe6, 0xa2, 0xe2, 0xa0, 0x1c}) +} + +func TestTimeStampMicrosGoZero(t *testing.T) { + testGoZeroTime(t, `{"type": "long", "logicalType": "timestamp-micros"}`, []byte{0xff, 0xff, 0xdd, 0xf2, 0xdf, 0xff, 0xdf, 0xdc, 0x1}) +} + +func TestDecimalBytesLogicalTypeEncode(t *testing.T) { + schema := `{"type": "bytes", "logicalType": "decimal", "precision": 4, "scale": 2}` + testBinaryCodecPass(t, schema, big.NewRat(617, 50), []byte("\x04\x04\xd2")) + testBinaryCodecPass(t, schema, big.NewRat(-617, 50), []byte("\x04\xfb\x2e")) + testBinaryCodecPass(t, schema, big.NewRat(0, 1), []byte("\x02\x00")) + // Test with a large decimal of precision 77 and scale 38 + largeDecimalSchema := `{"type": "bytes", "logicalType": "decimal", "precision": 77, "scale": 38}` + n, _ := new(big.Int).SetString("12345678901234567890123456789012345678911111111111111111111111111111111111111", 10) + d, _ := new(big.Int).SetString("100000000000000000000000000000000000000", 10) + largeRat := new(big.Rat).SetFrac(n, d) + testBinaryCodecPass(t, largeDecimalSchema, largeRat, []byte("\x40\x1b\x4b\x68\x19\x26\x11\xfa\xea\x20\x8f\xca\x21\x62\x7b\xe9\xda\xee\x32\x19\x83\x83\x95\x5d\xe8\x13\x1f\x4b\xf1\xc7\x1c\x71\xc7")) + +} + +func TestDecimalFixedLogicalTypeEncode(t *testing.T) { + schema := `{"type": "fixed", "size": 12, "logicalType": "decimal", "precision": 4, "scale": 2}` + testBinaryCodecPass(t, schema, big.NewRat(617, 50), []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\xd2")) + testBinaryCodecPass(t, schema, big.NewRat(-617, 50), []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfb\x2e")) + testBinaryCodecPass(t, schema, big.NewRat(25, 4), []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x71")) + testBinaryCodecPass(t, schema, big.NewRat(33, 100), []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x21")) + schema0scale := `{"type": "fixed", "size": 12, "logicalType": "decimal", "precision": 4, "scale": 0}` + // Encodes to 12 due to scale: 0 + testBinaryEncodePass(t, schema0scale, big.NewRat(617, 50), []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c")) + testBinaryDecodePass(t, schema0scale, big.NewRat(12, 1), []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c")) + + schemaPrecision1 := `{"type": "fixed", "size": 4, "logicalType": "decimal", "precision": 1, "scale": 1}` + testBinaryCodecPass(t, schemaPrecision1, big.NewRat(163, 10), []byte("\x00\x00\x00\xa3")) + testBinaryCodecPass(t, schemaPrecision1, big.NewRat(-130, 4), []byte("\xff\xff\xfe\xbb")) + testBinaryCodecPass(t, schemaPrecision1, big.NewRat(25, 2), []byte("\x00\x00\x00\x7d")) + testBinaryEncodeFail(t, schemaPrecision1, big.NewRat(math.MaxInt32, -1), "datum size ought to equal schema size") +} + +func TestDecimalBytesLogicalTypeInRecordEncode(t *testing.T) { + schema := `{"type": "record", "name": "myrecord", "fields" : [ + {"name": "mydecimal", "type": "bytes", "logicalType": "decimal", "precision": 4, "scale": 2}]}` + testBinaryCodecPass(t, schema, map[string]interface{}{"mydecimal": big.NewRat(617, 50)}, []byte("\x04\x04\xd2")) +} + +func TestValidatedStringLogicalTypeInRecordEncode(t *testing.T) { + schema := `{ + "type": "record", + "name": "myrecord", + "fields": [ + { + "name": "number", + "doc": "Phone number inside the national network. Length between 4-14", + "type": { + "type": "string", + "logicalType": "validatedString", + "pattern": "^[\\d]{4,14}$" + } + } + ] + }` + + codec, err := NewCodec(schema) + if err != nil { + t.Fatal(err) + } + + // NOTE: May omit fields when using default value + textual := []byte(`{"number": "667777777"}`) + + // Convert textual Avro data (in Avro JSON format) to native Go form + native, _, err := codec.NativeFromTextual(textual) + if err != nil { + t.Fatal(err) + } + + // Convert native Go form to binary Avro data + binary, err := codec.BinaryFromNative(nil, native) + if err != nil { + t.Fatal(err) + } + + testSchemaValid(t, schema) + testBinaryCodecPass(t, schema, map[string]interface{}{"number": "667777777"}, binary) + + // Convert binary Avro data back to native Go form + native, _, err = codec.NativeFromBinary(binary) + if err != nil { + t.Fatal(err) + } + + // Convert native Go form to textual Avro data + textual, err = codec.TextualFromNative(nil, native) + if err != nil { + t.Fatal(err) + } + + // NOTE: Textual encoding will show all fields, even those with values that + // match their default values + if got, want := string(textual), "{\"number\":\"667777777\"}"; got != want { + t.Errorf("GOT: %v; WANT: %v", got, want) + } +} + +func ExampleUnion_logicalType() { + // Supported logical types and their native go types: + // * timestamp-millis - time.Time + // * timestamp-micros - time.Time + // * time-millis - time.Duration + // * time-micros - time.Duration + // * date - int + // * decimal - big.Rat + codec, err := NewCodec(`["null", {"type": "long", "logicalType": "timestamp-millis"}]`) + if err != nil { + fmt.Println(err) + } + + // Note the usage of type.logicalType i.e. `long.timestamp-millis` to denote the type in a union. This is due to the single string naming format + // used by goavro. Decimal can be both bytes.decimal or fixed.decimal + bytes, err := codec.BinaryFromNative(nil, map[string]interface{}{"long.timestamp-millis": time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC)}) + if err != nil { + fmt.Println(err) + } + + decoded, _, err := codec.NativeFromBinary(bytes) + if err != nil { + fmt.Println(err) + } + out := decoded.(map[string]interface{}) + fmt.Printf("%#v\n", out["long.timestamp-millis"].(time.Time).String()) + // Output: "2006-01-02 15:04:05 +0000 UTC" +} + +func TestPrecisionAndScaleFromSchemaMapValidation(t *testing.T) { + testCasesInvalid := []struct { + schemaMap map[string]interface{} + errMsg string + }{ + {map[string]interface{}{}, "cannot create decimal logical type without precision"}, + {map[string]interface{}{ + precision: true, + }, "wrong precision type"}, + {map[string]interface{}{ + precision: float64(0), + }, "precision is less than one"}, + {map[string]interface{}{ + precision: float64(2), + scale: true, + }, "wrong scale type"}, + {map[string]interface{}{ + precision: float64(2), + scale: float64(-1), + }, "scale is less than zero"}, + {map[string]interface{}{ + precision: float64(2), + scale: float64(3), + }, "scale is larger than precision"}, + } + for _, tc := range testCasesInvalid { + _, _, err := precisionAndScaleFromSchemaMap(tc.schemaMap) + ensureError(t, err, tc.errMsg) + } + + // validation passes + p, s, err := precisionAndScaleFromSchemaMap(map[string]interface{}{ + precision: float64(1), + scale: float64(1), + }) + if p != 1 || s != 1 || err != nil { + t.Errorf("GOT: %v %v %v; WANT: 1 1 nil", p, s, err) + } +} diff --git a/fork/goavro/map.go b/fork/goavro/map.go new file mode 100644 index 0000000..5cd57a8 --- /dev/null +++ b/fork/goavro/map.go @@ -0,0 +1,307 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "errors" + "fmt" + "io" + "math" + "reflect" +) + +func makeMapCodec(converters map[string]ConvertBuild, st map[string]*Codec, namespace string, schemaMap map[string]interface{}, cb *codecBuilder) (*Codec, error) { + // map type must have values + valueSchema, ok := schemaMap["values"] + if !ok { + return nil, errors.New("Map ought to have values key") + } + valueCodec, err := buildCodec(converters, st, namespace, valueSchema, cb) + if err != nil { + return nil, fmt.Errorf("Map values ought to be valid Avro type: %s", err) + } + + return &Codec{ + typeName: &name{"map", nullNamespace}, + nativeFromBinary: func(buf []byte) (interface{}, []byte, error) { + var err error + var value interface{} + + // block count and block size + if value, buf, err = longNativeFromBinary(buf); err != nil { + return nil, nil, fmt.Errorf("cannot decode binary map block count: %s", err) + } + blockCount := value.(int64) + if blockCount < 0 { + // NOTE: A negative block count implies there is a long encoded + // block size following the negative block count. We have no use + // for the block size in this decoder, so we read and discard + // the value. + if blockCount == math.MinInt64 { + // The minimum number for any signed numerical type can + // never be made positive + return nil, nil, fmt.Errorf("cannot decode binary map with block count: %d", blockCount) + } + blockCount = -blockCount // convert to its positive equivalent + if _, buf, err = longNativeFromBinary(buf); err != nil { + return nil, nil, fmt.Errorf("cannot decode binary map block size: %s", err) + } + } + // Ensure block count does not exceed some sane value. + if blockCount > MaxBlockCount { + return nil, nil, fmt.Errorf("cannot decode binary map when block count exceeds MaxBlockCount: %d > %d", blockCount, MaxBlockCount) + } + // NOTE: While the attempt of a RAM optimization shown below is not + // necessary, many encoders will encode all items in a single block. + // We can optimize amount of RAM allocated by runtime for the array + // by initializing the array for that number of items. + mapValues := make(map[string]interface{}, blockCount) + + for blockCount != 0 { + // Decode `blockCount` datum values from buffer + for i := int64(0); i < blockCount; i++ { + // first decode the key string + if value, buf, err = stringNativeFromBinary(buf); err != nil { + return nil, nil, fmt.Errorf("cannot decode binary map key: %s", err) + } + key := value.(string) // string decoder always returns a string + if _, ok := mapValues[key]; ok { + return nil, nil, fmt.Errorf("cannot decode binary map: duplicate key: %q", key) + } + // then decode the value + if value, buf, err = valueCodec.nativeFromBinary(buf); err != nil { + return nil, nil, fmt.Errorf("cannot decode binary map value for key %q: %s", key, err) + } + mapValues[key] = value + } + // Decode next blockCount from buffer, because there may be more blocks + if value, buf, err = longNativeFromBinary(buf); err != nil { + return nil, nil, fmt.Errorf("cannot decode binary map block count: %s", err) + } + blockCount = value.(int64) + if blockCount < 0 { + // NOTE: A negative block count implies there is a long + // encoded block size following the negative block count. We + // have no use for the block size in this decoder, so we + // read and discard the value. + if blockCount == math.MinInt64 { + // The minimum number for any signed numerical type can + // never be made positive + return nil, nil, fmt.Errorf("cannot decode binary map with block count: %d", blockCount) + } + blockCount = -blockCount // convert to its positive equivalent + if _, buf, err = longNativeFromBinary(buf); err != nil { + return nil, nil, fmt.Errorf("cannot decode binary map block size: %s", err) + } + } + // Ensure block count does not exceed some sane value. + if blockCount > MaxBlockCount { + return nil, nil, fmt.Errorf("cannot decode binary map when block count exceeds MaxBlockCount: %d > %d", blockCount, MaxBlockCount) + } + } + return mapValues, buf, nil + }, + binaryFromNative: func(buf []byte, datum interface{}) ([]byte, error) { + mapValues, err := convertMap(datum) + if err != nil { + return nil, fmt.Errorf("cannot encode binary map: %s", err) + } + + keyCount := int64(len(mapValues)) + var alreadyEncoded, remainingInBlock int64 + + for k, v := range mapValues { + if remainingInBlock == 0 { // start a new block + remainingInBlock = keyCount - alreadyEncoded + if remainingInBlock > MaxBlockCount { + // limit block count to MacBlockCount + remainingInBlock = MaxBlockCount + } + buf, _ = longBinaryFromNative(buf, remainingInBlock) + } + + // only fails when given non string, so elide error checking + buf, _ = stringBinaryFromNative(buf, k) + + // encode the value + if buf, err = valueCodec.binaryFromNative(buf, v); err != nil { + return nil, fmt.Errorf("cannot encode binary map value for key %q: %v: %s", k, v, err) + } + + remainingInBlock-- + alreadyEncoded++ + } + return longBinaryFromNative(buf, 0) // append tailing 0 block count to signal end of Map + }, + nativeFromTextual: func(buf []byte) (interface{}, []byte, error) { + return genericMapTextDecoder(buf, valueCodec, nil) // codecFromKey == nil + }, + textualFromNative: func(buf []byte, datum interface{}) ([]byte, error) { + return genericMapTextEncoder(buf, datum, valueCodec, nil) + }, + }, nil +} + +// genericMapTextDecoder decodes a JSON text blob to a native Go map, using the +// codecs from codecFromKey, and if a key is not found in that map, from +// defaultCodec if provided. If defaultCodec is nil, this function returns an +// error if it encounters a map key that is not present in codecFromKey. If +// codecFromKey is nil, every map value will be decoded using defaultCodec, if +// possible. +func genericMapTextDecoder(buf []byte, defaultCodec *Codec, codecFromKey map[string]*Codec) (map[string]interface{}, []byte, error) { + var value interface{} + var err error + var b byte + + lencodec := len(codecFromKey) + mapValues := make(map[string]interface{}, lencodec) + + if buf, err = advanceAndConsume(buf, '{'); err != nil { + return nil, nil, err + } + if buf, _ = advanceToNonWhitespace(buf); len(buf) == 0 { + return nil, nil, io.ErrShortBuffer + } + // NOTE: Special case empty map + if buf[0] == '}' { + return mapValues, buf[1:], nil + } + + // NOTE: Also terminates when read '}' byte. + for len(buf) > 0 { + // decode key string + value, buf, err = stringNativeFromTextual(buf) + if err != nil { + return nil, nil, fmt.Errorf("cannot decode textual map: expected key: %s", err) + } + key := value.(string) + // Is key already used? + if _, ok := mapValues[key]; ok { + return nil, nil, fmt.Errorf("cannot decode textual map: duplicate key: %q", key) + } + // Find a codec for the key + fieldCodec := codecFromKey[key] + if fieldCodec == nil { + fieldCodec = defaultCodec + } + if fieldCodec == nil { + return nil, nil, fmt.Errorf("cannot decode textual map: cannot determine codec: %q", key) + } + // decode colon + if buf, err = advanceAndConsume(buf, ':'); err != nil { + return nil, nil, err + } + // decode value + if buf, _ = advanceToNonWhitespace(buf); len(buf) == 0 { + return nil, nil, io.ErrShortBuffer + } + value, buf, err = fieldCodec.nativeFromTextual(buf) + if err != nil { + return nil, nil, fmt.Errorf("%s for key: %q", err, key) + } + // set map value for key + mapValues[key] = value + // either comma or closing curly brace + if buf, _ = advanceToNonWhitespace(buf); len(buf) == 0 { + return nil, nil, io.ErrShortBuffer + } + switch b = buf[0]; b { + case '}': + return mapValues, buf[1:], nil + case ',': + // no-op + default: + return nil, nil, fmt.Errorf("cannot decode textual map: expected ',' or '}'; received: %q", b) + } + // NOTE: consume comma from above + if buf, _ = advanceToNonWhitespace(buf[1:]); len(buf) == 0 { + return nil, nil, io.ErrShortBuffer + } + } + return nil, nil, io.ErrShortBuffer +} + +// genericMapTextEncoder encodes a native Go map to a JSON text blob, using the +// codecs from codecFromKey, and if a key is not found in that map, from +// defaultCodec if provided. If defaultCodec is nil, this function returns an +// error if it encounters a map key that is not present in codecFromKey. If +// codecFromKey is nil, every map value will be encoded using defaultCodec, if +// possible. +func genericMapTextEncoder(buf []byte, datum interface{}, defaultCodec *Codec, codecFromKey map[string]*Codec) ([]byte, error) { + mapValues, err := convertMap(datum) + if err != nil { + return nil, fmt.Errorf("cannot encode textual map: %s", err) + } + + var atLeastOne bool + + buf = append(buf, '{') + + for key, value := range mapValues { + atLeastOne = true + + // Find a codec for the key + fieldCodec := codecFromKey[key] + if fieldCodec == nil { + fieldCodec = defaultCodec + } + if fieldCodec == nil { + return nil, fmt.Errorf("cannot encode textual map: cannot determine codec: %q", key) + } + // Encode key string + buf, err = stringTextualFromNative(buf, key) + if err != nil { + return nil, err + } + buf = append(buf, ':') + // Encode value + buf, err = fieldCodec.textualFromNative(buf, value) + if err != nil { + // field was specified in datum; therefore its value was invalid + return nil, fmt.Errorf("cannot encode textual map: value for %q does not match its schema: %s", key, err) + } + buf = append(buf, ',') + } + + if atLeastOne { + return append(buf[:len(buf)-1], '}'), nil + } + return append(buf, '}'), nil +} + +// convertMap converts datum to map[string]interface{} if possible. +func convertMap(datum interface{}) (map[string]interface{}, error) { + mapValues, ok := datum.(map[string]interface{}) + if ok { + return mapValues, nil + } + // NOTE: When given a map of any other type, zip values to items as a + // convenience to client. + v := reflect.ValueOf(datum) + if v.Kind() != reflect.Map { + return nil, fmt.Errorf("cannot create map[string]interface{}: expected map[string]...; received: %T", datum) + } + // NOTE: Two better alternatives to the current algorithm are: + // (1) mutate the reflection tuple underneath to convert the + // map[string]int, for example, to map[string]interface{}, with + // O(1) complexity. + // (2) use copy builtin to zip the data items over with O(n) complexity, + // but more efficient than what's below. + mapValues = make(map[string]interface{}, v.Len()) + for _, key := range v.MapKeys() { + k, ok := key.Interface().(string) + if !ok { + // bail when map key type is not string + return nil, fmt.Errorf("cannot create map[string]interface{}: expected map[string]...; received: %T", datum) + } + mapValues[string(k)] = v.MapIndex(key).Interface() + } + return mapValues, nil +} diff --git a/fork/goavro/map_test.go b/fork/goavro/map_test.go new file mode 100644 index 0000000..d1b02d2 --- /dev/null +++ b/fork/goavro/map_test.go @@ -0,0 +1,175 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "fmt" + "log" + "testing" +) + +func TestMapSchema(t *testing.T) { + // NOTE: This schema also used to read and write files in OCF format + testSchemaValid(t, `{"type":"map","values":"bytes"}`) + + testSchemaInvalid(t, `{"type":"map","value":"int"}`, "Map ought to have values key") + testSchemaInvalid(t, `{"type":"map","values":"integer"}`, "Map values ought to be valid Avro type") + testSchemaInvalid(t, `{"type":"map","values":3}`, "Map values ought to be valid Avro type") + testSchemaInvalid(t, `{"type":"map","values":int}`, "invalid character") // type name must be quoted +} + +func TestMapDecodeInitialBlockCountCannotDecode(t *testing.T) { + testBinaryDecodeFail(t, `{"type":"map","values":"int"}`, nil, "block count") +} + +func TestMapDecodeInitialBlockCountZero(t *testing.T) { + testBinaryDecodePass(t, `{"type":"map","values":"int"}`, map[string]interface{}{}, []byte{0}) +} + +func TestMapDecodeInitialBlockCountNegative(t *testing.T) { + testBinaryDecodePass(t, `{"type":"map","values":"int"}`, map[string]interface{}{"k1": 3}, []byte{1, 2, 4, 'k', '1', 6, 0}) +} + +func TestMapDecodeInitialBlockCountTooLarge(t *testing.T) { + testBinaryDecodeFail(t, `{"type":"map","values":"int"}`, morePositiveThanMaxBlockCount, "block count") +} + +func TestMapDecodeInitialBlockCountNegativeTooLarge(t *testing.T) { + testBinaryDecodeFail(t, `{"type":"map","values":"int"}`, append(moreNegativeThanMaxBlockCount, byte(0)), "block count") +} + +func TestMapDecodeInitialBlockCountTooNegative(t *testing.T) { + testBinaryDecodeFail(t, `{"type":"map","values":"int"}`, append(mostNegativeBlockCount, byte(0)), "block count") +} + +func TestMapDecodeNextBlockCountCannotDecode(t *testing.T) { + testBinaryDecodeFail(t, `{"type":"map","values":"int"}`, []byte{1, 2, 4, 'k', '1', 6}, "block count") +} + +func TestMapDecodeNextBlockCountNegative(t *testing.T) { + c, err := NewCodec(`{"type":"map","values":"int"}`) + if err != nil { + t.Fatal(err) + } + + decoded, _, err := c.NativeFromBinary([]byte{1, 2, 4, 'k', '1', 6, 1, 8, 4, 'k', '2', 0x1a, 0}) + if err != nil { + t.Fatal(err) + } + + decodedMap, ok := decoded.(map[string]interface{}) + if !ok { + t.Fatalf("GOT: %v; WANT: %v", ok, true) + } + + value, ok := decodedMap["k1"] + if !ok { + t.Errorf("GOT: %v; WANT: %v", ok, true) + } + if actual, expected := value.(int32), int32(3); actual != expected { + t.Errorf("GOT: %v; WANT: %v", actual, expected) + } + + value, ok = decodedMap["k2"] + if !ok { + t.Errorf("GOT: %v; WANT: %v", ok, true) + } + if actual, expected := value.(int32), int32(13); actual != expected { + t.Errorf("GOT: %v; WANT: %v", actual, expected) + } +} + +func TestMapDecodeNextBlockCountTooLarge(t *testing.T) { + testBinaryDecodeFail(t, `{"type":"map","values":"int"}`, append([]byte{1, 2, 4, 'k', '1', 6}, morePositiveThanMaxBlockCount...), "block count") +} + +func TestMapDecodeNextBlockCountNegativeTooLarge(t *testing.T) { + testBinaryDecodeFail(t, `{"type":"map","values":"int"}`, append(append([]byte{1, 2, 4, 'k', '1', 6}, moreNegativeThanMaxBlockCount...), 2), "block count") +} + +func TestMapDecodeNextBlockCountTooNegative(t *testing.T) { + testBinaryDecodeFail(t, `{"type":"map","values":"int"}`, append(append([]byte{1, 2, 4, 'k', '1', 6}, mostNegativeBlockCount...), 2), "block count") +} + +func TestMapDecodeFail(t *testing.T) { + schema := `{"type":"map","values":"boolean"}` + testBinaryDecodeFail(t, schema, nil, "cannot decode binary map block count") // leading block count + testBinaryDecodeFail(t, schema, []byte("\x01"), "cannot decode binary map block size") // when block count < 0 + testBinaryDecodeFail(t, schema, []byte("\x02\x04"), "cannot decode binary map key") + testBinaryDecodeFail(t, schema, []byte("\x02\x04"), "cannot decode binary map key") + testBinaryDecodeFail(t, schema, []byte("\x02\x04a"), "cannot decode binary map key") + testBinaryDecodeFail(t, schema, []byte("\x02\x04ab"), `cannot decode binary map value for key "ab"`) + testBinaryDecodeFail(t, schema, []byte("\x02\x04ab\x02"), "boolean: expected") + testBinaryDecodeFail(t, schema, []byte("\x02\x04ab\x01"), "cannot decode binary map block count") // trailing block count + testBinaryDecodeFail(t, schema, []byte("\x04\x04ab\x00\x04ab\x00\x00"), "duplicate key") +} + +func TestMap(t *testing.T) { + testBinaryCodecPass(t, `{"type":"map","values":"null"}`, map[string]interface{}{"ab": nil}, []byte("\x02\x04ab\x00")) + testBinaryCodecPass(t, `{"type":"map","values":"boolean"}`, map[string]interface{}{"ab": true}, []byte("\x02\x04ab\x01\x00")) +} + +func TestMapTextDecodeFail(t *testing.T) { + schema := `{"type":"map","values":"string"}` + testTextDecodeFail(t, schema, []byte(` "string" : "silly" , "bytes" : "silly" } `), "expected: '{'") + testTextDecodeFail(t, schema, []byte(` { 16 : "silly" , "bytes" : "silly" } `), "expected initial \"") + testTextDecodeFail(t, schema, []byte(` { "string" , "silly" , "bytes" : "silly" } `), "expected: ':'") + testTextDecodeFail(t, schema, []byte(` { "string" : 13 , "bytes" : "silly" } `), "expected initial \"") + testTextDecodeFail(t, schema, []byte(` { "string" : "silly" : "bytes" : "silly" } `), "expected ',' or '}'") + testTextDecodeFail(t, schema, []byte(` { "string" : "silly" "bytes" : "silly" } `), "expected ',' or '}'") + testTextDecodeFail(t, schema, []byte(` { "string" : "silly" , "bytes" : "silly" `), "short buffer") + testTextDecodeFail(t, schema, []byte(` { "string" : "silly" `), "short buffer") + testTextDecodeFail(t, schema, []byte(`{"key1":"\u0001\u2318 ","key1":"value2"}`), "duplicate key") +} + +func TestMapTextCodecPass(t *testing.T) { + schema := `{"type":"map","values":"string"}` + datum := map[string]interface{}{"key1": "⌘ "} + testTextCodecPass(t, schema, make(map[string]interface{}), []byte(`{}`)) // empty map + testTextEncodePass(t, schema, datum, []byte(`{"key1":"\u0001\u2318 "}`)) + testTextDecodePass(t, schema, datum, []byte(` { "key1" : "\u0001\u2318 " }`)) +} + +func TestMapBinaryReceiveSliceInt(t *testing.T) { + testBinaryCodecPass(t, `{"type":"map","values":"int"}`, map[string]int{}, []byte("\x00")) + testBinaryCodecPass(t, `{"type":"map","values":"int"}`, map[string]int{"k1": 13}, []byte("\x02\x04k1\x1a\x00")) + testBinaryEncodeFail(t, `{"type":"map","values":"int"}`, map[int]int{42: 13}, "cannot create map[string]interface{}") +} + +func TestMapTextualReceiveSliceInt(t *testing.T) { + testTextCodecPass(t, `{"type":"map","values":"int"}`, map[string]int{}, []byte(`{}`)) + testTextCodecPass(t, `{"type":"map","values":"int"}`, map[string]int{"k1": 13}, []byte(`{"k1":13}`)) + testTextEncodeFail(t, `{"type":"map","values":"int"}`, map[int]int{42: 13}, "cannot create map[string]interface{}") +} + +func ExampleMap() { + codec, err := NewCodec(`{ + "name": "r1", + "type": "record", + "fields": [{ + "name": "f1", + "type": {"type":"map","values":"double"} + }] + }`) + if err != nil { + log.Fatal(err) + } + + buf, err := codec.TextualFromNative(nil, map[string]interface{}{ + "f1": map[string]float64{ + "k1": 3.5, + }, + }) + if err != nil { + log.Fatal(err) + } + fmt.Println(string(buf)) + // Output: {"f1":{"k1":3.5}} +} diff --git a/fork/goavro/name.go b/fork/goavro/name.go new file mode 100644 index 0000000..2a36726 --- /dev/null +++ b/fork/goavro/name.go @@ -0,0 +1,142 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "errors" + "fmt" + "strings" +) + +const nullNamespace = "" + +// ErrInvalidName is the error returned when one or more parts of an Avro name +// is invalid. +type ErrInvalidName struct { + Message string +} + +func (e ErrInvalidName) Error() string { + return "schema name ought to " + e.Message +} + +// NOTE: This function designed to work with name components, after they have +// been split on the period rune. +func isRuneInvalidForFirstCharacter(r rune) bool { + return (r < 'A' || r > 'Z') && (r < 'a' || r > 'z') && r != '_' +} + +func isRuneInvalidForOtherCharacters(r rune) bool { + return isRuneInvalidForFirstCharacter(r) && (r < '0' || r > '9') +} + +func checkNameComponent(s string) error { + err := checkString(s) + if err != nil { + return &ErrInvalidName{err.Error()} + } + return err +} + +func checkString(s string) error { + if len(s) == 0 { + return errors.New("be non-empty string") + } + if strings.IndexFunc(s[:1], isRuneInvalidForFirstCharacter) != -1 { + return errors.New("start with [A-Za-z_]: " + s) + } + if strings.IndexFunc(s[1:], isRuneInvalidForOtherCharacters) != -1 { + return errors.New("have second and remaining characters contain only [A-Za-z0-9_]: " + s) + } + return nil +} + +// name describes an Avro name in terms of its full name and namespace. +type name struct { + fullName string // the instance's Avro name + namespace string // for use when building new name from existing one +} + +// newName returns a new Name instance after first ensuring the arguments do not +// violate any of the Avro naming rules. +func newName(n, ns, ens string) (*name, error) { + var nn name + + if index := strings.LastIndexByte(n, '.'); index > -1 { + // inputName does contain a dot, so ignore everything else and use it as the full name + nn.fullName = n + nn.namespace = n[:index] + } else { + // inputName does not contain a dot, therefore is not the full name + if ns != nullNamespace { + // if namespace provided in the schema in the same schema level, use it + nn.fullName = ns + "." + n + nn.namespace = ns + } else if ens != nullNamespace { + // otherwise if enclosing namespace provided, use it + nn.fullName = ens + "." + n + nn.namespace = ens + } else { + // otherwise no namespace, so use null namespace, the empty string + nn.fullName = n + } + } + + // verify all components of the full name for adherence to Avro naming rules + for i, component := range strings.Split(nn.fullName, ".") { + if i == 0 && RelaxedNameValidation && component == "" { + continue + } + if err := checkNameComponent(component); err != nil { + return nil, err + } + } + + return &nn, nil +} + +var ( + // RelaxedNameValidation causes name validation to allow the first component + // of an Avro namespace to be the empty string. + RelaxedNameValidation bool +) + +func newNameFromSchemaMap(enclosingNamespace string, schemaMap map[string]interface{}) (*name, error) { + var nameString, namespaceString string + + name, ok := schemaMap["name"] + if !ok { + return nil, errors.New("schema ought to have name key") + } + nameString, ok = name.(string) + if !ok || nameString == nullNamespace { + return nil, fmt.Errorf("schema name ought to be non-empty string; received: %T: %v", name, name) + } + if namespace, ok := schemaMap["namespace"]; ok { + namespaceString, ok = namespace.(string) + if !ok { + return nil, fmt.Errorf("schema namespace, if provided, ought to be a string; received: %T: %v", namespace, namespace) + } + } + + return newName(nameString, namespaceString, enclosingNamespace) +} + +func (n *name) String() string { + return n.fullName +} + +// short returns the name without the prefixed namespace. +func (n *name) short() string { + if index := strings.LastIndexByte(n.fullName, '.'); index > -1 { + return n.fullName[index+1:] + } + return n.fullName +} diff --git a/fork/goavro/name_test.go b/fork/goavro/name_test.go new file mode 100644 index 0000000..afb4483 --- /dev/null +++ b/fork/goavro/name_test.go @@ -0,0 +1,100 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +// NOTE: part of goavro package because it tests private functionality + +import ( + "testing" +) + +func TestNameStartsInvalidCharacter(t *testing.T) { + _, err := newName("&X", "org.foo", nullNamespace) + if _, ok := err.(ErrInvalidName); err == nil && !ok { + t.Errorf("GOT: %#v, WANT: %#v", err, ErrInvalidName{"start with [A-Za-z_]"}) + } +} + +func TestNameContainsInvalidCharacter(t *testing.T) { + _, err := newName("X&", "org.foo.bar", nullNamespace) + if _, ok := err.(ErrInvalidName); err == nil && !ok { + t.Errorf("GOT: %#v, WANT: %#v", err, ErrInvalidName{"start with [A-Za-z_]"}) + } +} + +func TestNamespaceContainsInvalidCharacter(t *testing.T) { + defer func() { RelaxedNameValidation = false }() + RelaxedNameValidation = true + n, err := newName("X", ".org.foo", nullNamespace) + if err != nil { + t.Fatal(err) + } + if actual, expected := n.fullName, ".org.foo.X"; actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } + if actual, expected := n.namespace, ".org.foo"; actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } +} + +func TestNameAndNamespaceProvided(t *testing.T) { + n, err := newName("X", "org.foo", nullNamespace) + if err != nil { + t.Fatal(err) + } + if actual, expected := n.fullName, "org.foo.X"; actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } + if actual, expected := n.namespace, "org.foo"; actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } +} + +func TestNameWithDotIgnoresNamespace(t *testing.T) { + n, err := newName("org.bar.X", "some.ignored.namespace", nullNamespace) + if err != nil { + t.Fatal(err) + } + if actual, expected := n.fullName, "org.bar.X"; actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } + if actual, expected := n.namespace, "org.bar"; actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } +} + +func TestNameWithoutDotsButWithEmptyNamespaceAndEnclosingName(t *testing.T) { + n, err := newName("X", nullNamespace, "org.foo") + if err != nil { + t.Fatal(err) + } + if actual, expected := n.fullName, "org.foo.X"; actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } + if actual, expected := n.namespace, "org.foo"; actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } +} + +func TestNewNameFromSchemaMap(t *testing.T) { + n, err := newNameFromSchemaMap(nullNamespace, map[string]interface{}{ + "name": "foo", + "namespace": "", + "type": map[string]interface{}{}, + }) + ensureError(t, err) + + if got, want := n.fullName, "foo"; got != want { + t.Errorf("GOT: %q; WANT: %q", got, want) + } + if got, want := n.namespace, ""; got != want { + t.Errorf("GOT: %q; WANT: %q", got, want) + } +} diff --git a/fork/goavro/null.go b/fork/goavro/null.go new file mode 100644 index 0000000..bae44b8 --- /dev/null +++ b/fork/goavro/null.go @@ -0,0 +1,45 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "bytes" + "errors" + "fmt" + "io" +) + +var nullBytes = []byte("null") + +func nullNativeFromBinary(buf []byte) (interface{}, []byte, error) { return nil, buf, nil } + +func nullBinaryFromNative(buf []byte, datum interface{}) ([]byte, error) { + if datum != nil { + return nil, fmt.Errorf("cannot encode binary null: expected: Go nil; received: %T", datum) + } + return buf, nil +} + +func nullNativeFromTextual(buf []byte) (interface{}, []byte, error) { + if len(buf) < 4 { + return nil, nil, fmt.Errorf("cannot decode textual null: %s", io.ErrShortBuffer) + } + if bytes.Equal(buf[:4], nullBytes) { + return nil, buf[4:], nil + } + return nil, nil, errors.New("cannot decode textual null: expected: null") +} + +func nullTextualFromNative(buf []byte, datum interface{}) ([]byte, error) { + if datum != nil { + return nil, fmt.Errorf("cannot encode textual null: expected: Go nil; received: %T", datum) + } + return append(buf, nullBytes...), nil +} diff --git a/fork/goavro/null_test.go b/fork/goavro/null_test.go new file mode 100644 index 0000000..328f13a --- /dev/null +++ b/fork/goavro/null_test.go @@ -0,0 +1,26 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import "testing" + +func TestSchemaPrimitiveNullCodec(t *testing.T) { + testSchemaPrimativeCodec(t, `"null"`) +} + +func TestPrimitiveNullBinary(t *testing.T) { + testBinaryEncodeFailBadDatumType(t, `"null"`, false) + testBinaryCodecPass(t, `"null"`, nil, nil) +} + +func TestPrimitiveNullText(t *testing.T) { + testTextEncodeFailBadDatumType(t, `"null"`, false) + testTextCodecPass(t, `"null"`, nil, []byte("null")) +} diff --git a/fork/goavro/number_recover_test.go b/fork/goavro/number_recover_test.go new file mode 100644 index 0000000..b47d2b5 --- /dev/null +++ b/fork/goavro/number_recover_test.go @@ -0,0 +1,65 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "reflect" + "testing" +) + +func testPrimitiveRecoverNative(t *testing.T, schema string, value interface{}) { + t.Helper() + codec, err := NewCodec(schema) + if err != nil { + t.Fatalf("Schema: %s; %s", schema, err) + } + + // native -> binary -> native + binary, err := codec.BinaryFromNative(nil, value) + if err != nil { + t.Fatalf("Datum: %v; %s", value, err) + } + native, _, err := codec.NativeFromBinary(binary) + if err != nil { + t.Fatalf("Datum: %s; %s", binary, err) + } + if reflect.TypeOf(value) != reflect.TypeOf(native) { + t.Fatalf("Datum: %v expected type %T but was value %v of type %T", value, value, native, native) + } + + // native -> textual -> native + textual, err := codec.TextualFromNative(nil, value) + if err != nil { + t.Fatalf("Datum: %v; %s", value, err) + } + native, _, err = codec.NativeFromTextual(textual) + if err != nil { + t.Fatalf("Datum: %s; %s", textual, err) + } + if reflect.TypeOf(value) != reflect.TypeOf(native) { + t.Fatalf("Datum: %v expected type %T but was value %v of type %T", value, value, native, native) + } +} + +func TestPrimitiveRecoverInt(t *testing.T) { + testPrimitiveRecoverNative(t, `"int"`, int32(1010)) +} + +func TestPrimitiveRecoverLong(t *testing.T) { + testPrimitiveRecoverNative(t, `"long"`, int64(8128953)) +} + +func TestPrimitiveRecoverFloat(t *testing.T) { + testPrimitiveRecoverNative(t, `"float"`, float32(-8.937134)) +} + +func TestPrimitiveRecoverDouble(t *testing.T) { + testPrimitiveRecoverNative(t, `"double"`, float64(5.247290238727473)) +} diff --git a/fork/goavro/ocf.go b/fork/goavro/ocf.go new file mode 100644 index 0000000..61f703a --- /dev/null +++ b/fork/goavro/ocf.go @@ -0,0 +1,240 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "bytes" + "crypto/rand" + "errors" + "fmt" + "io" +) + +const ( + // CompressionNullLabel is used when OCF blocks are not compressed. + CompressionNullLabel = "null" + + // CompressionDeflateLabel is used when OCF blocks are compressed using the + // deflate algorithm. + CompressionDeflateLabel = "deflate" + + // CompressionSnappyLabel is used when OCF blocks are compressed using the + // snappy algorithm. + CompressionSnappyLabel = "snappy" +) + +// compressionID are values used to specify compression algorithm used to compress +// and decompress Avro Object Container File (OCF) streams. +type compressionID uint8 + +const ( + compressionNull compressionID = iota + compressionDeflate + compressionSnappy +) + +const ( + ocfBlockConst = 24 // Each OCF block has two longs prefix, and sync marker suffix + ocfHeaderSizeConst = 48 // OCF header is usually about 48 bytes longer than its compressed schema + ocfMagicString = "Obj\x01" + ocfMetadataSchema = `{"type":"map","values":"bytes"}` + ocfSyncLength = 16 +) + +var ( + ocfMagicBytes = []byte(ocfMagicString) + ocfMetadataCodec *Codec +) + +func init() { + ocfMetadataCodec, _ = NewCodec(ocfMetadataSchema) +} + +type ocfHeader struct { + codec *Codec + compressionID compressionID + syncMarker [ocfSyncLength]byte + metadata map[string][]byte +} + +func newOCFHeader(config OCFConfig) (*ocfHeader, error) { + var err error + + header := new(ocfHeader) + + // + // avro.codec + // + switch config.CompressionName { + case "": + header.compressionID = compressionNull + case CompressionNullLabel: + header.compressionID = compressionNull + case CompressionDeflateLabel: + header.compressionID = compressionDeflate + case CompressionSnappyLabel: + header.compressionID = compressionSnappy + default: + return nil, fmt.Errorf("cannot create OCF header using unrecognized compression algorithm: %q", config.CompressionName) + } + + // + // avro.schema + // + if config.Codec != nil { + header.codec = config.Codec + } else if config.Schema == "" { + return nil, fmt.Errorf("cannot create OCF header without either Codec or Schema specified") + } else { + if header.codec, err = NewCodec(config.Schema); err != nil { + return nil, fmt.Errorf("cannot create OCF header: %s", err) + } + } + + header.metadata = config.MetaData + + // + // The 16-byte, randomly-generated sync marker for this file. + // + _, err = rand.Read(header.syncMarker[:]) + if err != nil { + return nil, err + } + + return header, nil +} + +func readOCFHeader(ior io.Reader) (*ocfHeader, error) { + // + // magic bytes + // + magic := make([]byte, 4) + _, err := io.ReadFull(ior, magic) + if err != nil { + return nil, fmt.Errorf("cannot read OCF header magic bytes: %s", err) + } + if !bytes.Equal(magic, ocfMagicBytes) { + return nil, fmt.Errorf("cannot read OCF header with invalid magic bytes: %#q", magic) + } + + // + // metadata + // + metadata, err := metadataBinaryReader(ior) + if err != nil { + return nil, fmt.Errorf("cannot read OCF header metadata: %s", err) + } + + // + // avro.codec + // + // NOTE: Avro specification states that `null` cID is used by + // default when "avro.codec" was not included in the metadata header. The + // specification does not talk about the case when "avro.codec" was included + // with the empty string as its value. I believe it is an error for an OCF + // file to provide the empty string as the cID algorithm. While it + // is trivially easy to gracefully handle here, I'm not sure whether this + // happens a lot, and don't want to accept bad input unless we have + // significant reason to do so. + var cID compressionID + value, ok := metadata["avro.codec"] + if ok { + switch avroCodec := string(value); avroCodec { + case CompressionNullLabel: + cID = compressionNull + case CompressionDeflateLabel: + cID = compressionDeflate + case CompressionSnappyLabel: + cID = compressionSnappy + default: + return nil, fmt.Errorf("cannot read OCF header using unrecognized compression algorithm from avro.codec: %q", avroCodec) + } + } + + // + // create goavro.Codec from specified avro.schema + // + value, ok = metadata["avro.schema"] + if !ok { + return nil, errors.New("cannot read OCF header without avro.schema") + } + codec, err := NewCodec(string(value)) + if err != nil { + return nil, fmt.Errorf("cannot read OCF header with invalid avro.schema: %s", err) + } + + header := &ocfHeader{codec: codec, compressionID: cID, metadata: metadata} + + // + // read and store sync marker + // + if n, err := io.ReadFull(ior, header.syncMarker[:]); err != nil { + return nil, fmt.Errorf("cannot read OCF header without sync marker: only read %d of %d bytes: %s", n, ocfSyncLength, err) + } + + // + // header is valid + // + return header, nil +} + +func writeOCFHeader(header *ocfHeader, iow io.Writer) (err error) { + // + // avro.codec + // + var avroCodec string + switch header.compressionID { + case compressionNull: + avroCodec = CompressionNullLabel + case compressionDeflate: + avroCodec = CompressionDeflateLabel + case compressionSnappy: + avroCodec = CompressionSnappyLabel + default: + return fmt.Errorf("should not get here: cannot write OCF header using unrecognized compression algorithm: %d", header.compressionID) + } + + // + // avro.schema + // + // Create buffer for OCF header. The first four bytes are magic, and we'll + // use copy to fill them in, so initialize buffer's length with 4, and its + // capacity equal to length of avro schema plus a constant. + schema := header.codec.Schema() + buf := make([]byte, 4, len(schema)+ocfHeaderSizeConst) + _ = copy(buf, ocfMagicBytes) + + // + // file metadata, including the schema + // + meta := make(map[string]interface{}) + for k, v := range header.metadata { + meta[k] = v + } + meta["avro.schema"] = []byte(schema) + meta["avro.codec"] = []byte(avroCodec) + + buf, err = ocfMetadataCodec.BinaryFromNative(buf, meta) + if err != nil { + return fmt.Errorf("should not get here: cannot write OCF header: %s", err) + } + + // + // 16-byte sync marker + // + buf = append(buf, header.syncMarker[:]...) + + // emit OCF header + _, err = iow.Write(buf) + if err != nil { + return fmt.Errorf("cannot write OCF header: %s", err) + } + return nil +} diff --git a/fork/goavro/ocf_reader.go b/fork/goavro/ocf_reader.go new file mode 100644 index 0000000..fbcc696 --- /dev/null +++ b/fork/goavro/ocf_reader.go @@ -0,0 +1,263 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "bytes" + "compress/flate" + "encoding/binary" + "errors" + "fmt" + "hash/crc32" + "io" + "io/ioutil" + + "github.com/golang/snappy" +) + +// OCFReader structure is used to read Object Container Files (OCF). +type OCFReader struct { + header *ocfHeader + block []byte // buffer from which decoding takes place + rerr error // most recent error that took place while reading bytes (unrecoverable) + ior io.Reader + readReady bool // true after Scan and before Read + remainingBlockItems int64 // count of encoded data items remaining in block buffer to be decoded +} + +// NewOCFReader initializes and returns a new structure used to read an Avro +// Object Container File (OCF). +// +// func example(ior io.Reader) error { +// // NOTE: Wrap provided io.Reader in a buffered reader, which improves the +// // performance of streaming file data. +// br := bufio.NewReader(ior) +// ocfr, err := goavro.NewOCFReader(br) +// if err != nil { +// return err +// } +// for ocfr.Scan() { +// datum, err := ocfr.Read() +// if err != nil { +// return err +// } +// fmt.Println(datum) +// } +// return ocfr.Err() +// } +func NewOCFReader(ior io.Reader) (*OCFReader, error) { + header, err := readOCFHeader(ior) + if err != nil { + return nil, fmt.Errorf("cannot create OCFReader: %s", err) + } + return &OCFReader{header: header, ior: ior}, nil +} + +//MetaData returns the file metadata map found within the OCF file +func (ocfr *OCFReader) MetaData() map[string][]byte { + return ocfr.header.metadata +} + +// Codec returns the codec found within the OCF file. +func (ocfr *OCFReader) Codec() *Codec { + return ocfr.header.codec +} + +// CompressionName returns the name of the compression algorithm found within +// the OCF file. +func (ocfr *OCFReader) CompressionName() string { + switch ocfr.header.compressionID { + case compressionNull: + return CompressionNullLabel + case compressionDeflate: + return CompressionDeflateLabel + case compressionSnappy: + return CompressionSnappyLabel + default: + return "should not get here: unrecognized compression algorithm" + } +} + +// Err returns the last error encountered while reading the OCF file. See +// `NewOCFReader` documentation for an example. +func (ocfr *OCFReader) Err() error { + return ocfr.rerr +} + +// Read consumes one datum value from the Avro OCF stream and returns it. Read +// is designed to be called only once after each invocation of the Scan method. +// See `NewOCFReader` documentation for an example. +func (ocfr *OCFReader) Read() (interface{}, error) { + // NOTE: Test previous error before testing readReady to prevent overwriting + // previous error. + if ocfr.rerr != nil { + return nil, ocfr.rerr + } + if !ocfr.readReady { + ocfr.rerr = errors.New("Read called without successful Scan") + return nil, ocfr.rerr + } + ocfr.readReady = false + + // decode one datum value from block + var datum interface{} + datum, ocfr.block, ocfr.rerr = ocfr.header.codec.NativeFromBinary(ocfr.block) + if ocfr.rerr != nil { + return false, ocfr.rerr + } + ocfr.remainingBlockItems-- + + return datum, nil +} + +// RemainingBlockItems returns the number of items remaining in the block being +// processed. +func (ocfr *OCFReader) RemainingBlockItems() int64 { + return ocfr.remainingBlockItems +} + +// Scan returns true when there is at least one more data item to be read from +// the Avro OCF. Scan ought to be called prior to calling the Read method each +// time the Read method is invoked. See `NewOCFReader` documentation for an +// example. +func (ocfr *OCFReader) Scan() bool { + ocfr.readReady = false + + if ocfr.rerr != nil { + return false + } + + // NOTE: If there are no more remaining data items from the existing block, + // then attempt to slurp in the next block. + if ocfr.remainingBlockItems <= 0 { + if count := len(ocfr.block); count != 0 { + ocfr.rerr = fmt.Errorf("extra bytes between final datum in previous block and block sync marker: %d", count) + return false + } + + // Read the block count and update the number of remaining items for + // this block + ocfr.remainingBlockItems, ocfr.rerr = longBinaryReader(ocfr.ior) + if ocfr.rerr != nil { + if ocfr.rerr == io.EOF { + ocfr.rerr = nil // merely end of file, rather than error + } else { + ocfr.rerr = fmt.Errorf("cannot read block count: %s", ocfr.rerr) + } + return false + } + if ocfr.remainingBlockItems <= 0 { + ocfr.rerr = fmt.Errorf("cannot decode when block count is not greater than 0: %d", ocfr.remainingBlockItems) + return false + } + if ocfr.remainingBlockItems > MaxBlockCount { + ocfr.rerr = fmt.Errorf("cannot decode when block count exceeds MaxBlockCount: %d > %d", ocfr.remainingBlockItems, MaxBlockCount) + } + + var blockSize int64 + blockSize, ocfr.rerr = longBinaryReader(ocfr.ior) + if ocfr.rerr != nil { + ocfr.rerr = fmt.Errorf("cannot read block size: %s", ocfr.rerr) + return false + } + if blockSize <= 0 { + ocfr.rerr = fmt.Errorf("cannot decode when block size is not greater than 0: %d", blockSize) + return false + } + if blockSize > MaxBlockSize { + ocfr.rerr = fmt.Errorf("cannot decode when block size exceeds MaxBlockSize: %d > %d", blockSize, MaxBlockSize) + return false + } + + // read entire block into buffer + ocfr.block = make([]byte, blockSize) + _, ocfr.rerr = io.ReadFull(ocfr.ior, ocfr.block) + if ocfr.rerr != nil { + ocfr.rerr = fmt.Errorf("cannot read block: %s", ocfr.rerr) + return false + } + + switch ocfr.header.compressionID { + case compressionNull: + // no-op + + case compressionDeflate: + // NOTE: flate.NewReader wraps with io.ByteReader if argument does + // not implement that interface. + rc := flate.NewReader(bytes.NewBuffer(ocfr.block)) + ocfr.block, ocfr.rerr = ioutil.ReadAll(rc) + if ocfr.rerr != nil { + _ = rc.Close() + return false + } + if ocfr.rerr = rc.Close(); ocfr.rerr != nil { + return false + } + + case compressionSnappy: + index := len(ocfr.block) - 4 // last 4 bytes is crc32 of decoded block + if index <= 0 { + ocfr.rerr = fmt.Errorf("cannot decompress snappy without CRC32 checksum: %d", len(ocfr.block)) + return false + } + decoded, err := snappy.Decode(nil, ocfr.block[:index]) + if err != nil { + ocfr.rerr = fmt.Errorf("cannot decompress: %s", err) + return false + } + actualCRC := crc32.ChecksumIEEE(decoded) + expectedCRC := binary.BigEndian.Uint32(ocfr.block[index : index+4]) + if actualCRC != expectedCRC { + ocfr.rerr = fmt.Errorf("snappy CRC32 checksum mismatch: %x != %x", actualCRC, expectedCRC) + return false + } + ocfr.block = decoded + + default: + ocfr.rerr = fmt.Errorf("should not get here: cannot compress block using unrecognized compression: %d", ocfr.header.compressionID) + return false + + } + + // read and ensure sync marker matches + sync := make([]byte, ocfSyncLength) + var n int + if n, ocfr.rerr = io.ReadFull(ocfr.ior, sync); ocfr.rerr != nil { + ocfr.rerr = fmt.Errorf("cannot read sync marker: read %d out of %d bytes: %s", n, ocfSyncLength, ocfr.rerr) + return false + } + if !bytes.Equal(sync, ocfr.header.syncMarker[:]) { + ocfr.rerr = fmt.Errorf("sync marker mismatch: %v != %v", sync, ocfr.header.syncMarker) + return false + } + } + + ocfr.readReady = true + return true +} + +// SkipThisBlockAndReset can be called after an error occurs while reading or +// decoding datum values from an OCF stream. OCF specifies each OCF stream +// contain one or more blocks of data. Each block consists of a block count, the +// number of bytes for the block, followed be the possibly compressed +// block. Inside each decompressed block is all of the binary encoded datum +// values concatenated together. In other words, OCF framing is at a block level +// rather than a datum level. If there is an error while reading or decoding a +// datum, the reader is not able to skip to the next datum value, because OCF +// does not have any markers for where each datum ends and the next one +// begins. Therefore, the reader is only able to skip this datum value and all +// subsequent datum values in the current block, move to the next block and +// start decoding datum values there. +func (ocfr *OCFReader) SkipThisBlockAndReset() { + // ??? is it an error to call method unless the reader has had an error + ocfr.remainingBlockItems = 0 + ocfr.block = ocfr.block[:0] + ocfr.rerr = nil +} diff --git a/fork/goavro/ocf_reader_test.go b/fork/goavro/ocf_reader_test.go new file mode 100644 index 0000000..ced2a56 --- /dev/null +++ b/fork/goavro/ocf_reader_test.go @@ -0,0 +1,102 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "bytes" + "testing" +) + +// readOCFHeader, magic bytes + +func TestReadOCFHeaderMagicBytes(t *testing.T) { + _, err := NewOCFReader(bytes.NewBuffer([]byte("Obj"))) // missing fourth byte + ensureError(t, err, "cannot create OCF") + + _, err = NewOCFReader(bytes.NewBuffer([]byte("...."))) + ensureError(t, err, "cannot create OCF") +} + +// +// cannot read OCF header +// + +func testCannotReadOCFHeader(t *testing.T, input []byte, expected ...string) { + t.Helper() + _, err := NewOCFReader(bytes.NewBuffer(append([]byte("Obj\x01"), input...))) + ensureError(t, err, append([]string{"cannot read OCF header"}, expected...)...) +} + +// readOCFHeader, metadataBinaryReader, block count + +func TestReadOCFHeaderMetadataBinaryReaderBlockCount(t *testing.T) { + testCannotReadOCFHeader(t, nil, "cannot read map block count", "EOF") + testCannotReadOCFHeader(t, mostNegativeBlockCount, "cannot read map with block count") + testCannotReadOCFHeader(t, []byte("\x01"), "cannot read map block size", "EOF") + testCannotReadOCFHeader(t, morePositiveThanMaxBlockCount, "cannot read map when block count exceeds") +} + +// readOCFHeader, metadataBinaryReader, bytesBinaryReader + +func TestReadOCFHeaderMetadataBinaryReaderMapKey(t *testing.T) { + testCannotReadOCFHeader(t, []byte("\x02"), "cannot read map key", "cannot read bytes", "cannot read size", "EOF") + testCannotReadOCFHeader(t, []byte("\x02\x01"), "cannot read map key", "cannot read bytes", "size is negative") + testCannotReadOCFHeader(t, append([]byte("\x02"), morePositiveThanMaxBlockCount...), "cannot read map key", "cannot read bytes", "size exceeds MaxBlockSize") + testCannotReadOCFHeader(t, append([]byte("\x02"), mostNegativeBlockCount...), "cannot read map key", "cannot read bytes", "size is negative") + testCannotReadOCFHeader(t, append([]byte("\x02"), moreNegativeThanMaxBlockCount...), "cannot read map key", "cannot read bytes", "size is negative") + testCannotReadOCFHeader(t, []byte("\x02\x02"), "cannot read map key", "cannot read bytes", "EOF") + testCannotReadOCFHeader(t, []byte("\x02\x04k1\x04v1\x02\x04k1"), "cannot read map", "duplicate key") + testCannotReadOCFHeader(t, []byte("\x04\x04k1\x04v1\x04k1"), "cannot read map", "duplicate key") +} + +func TestReadOCFHeaderMetadataBinaryReaderMapValue(t *testing.T) { + testCannotReadOCFHeader(t, []byte("\x02\x04k1"), "cannot read map value for key", "cannot read bytes", "EOF") + // have already tested all other binaryBytesReader errors above + testCannotReadOCFHeader(t, []byte("\x02\x04k1\x04v1"), "cannot read map block count", "EOF") + testCannotReadOCFHeader(t, append([]byte("\x02\x04k1\x04v1"), mostNegativeBlockCount...), "cannot read map with block count") + testCannotReadOCFHeader(t, []byte("\x02\x04k1\x04v1"), "cannot read map block count", "EOF") + testCannotReadOCFHeader(t, []byte("\x02\x04k1\x04v1\x01"), "cannot read map block size", "EOF") + testCannotReadOCFHeader(t, append(append([]byte("\x02\x04k1\x04v1"), moreNegativeThanMaxBlockCount...), []byte("\x02")...), "cannot read map when block count exceeds") + testCannotReadOCFHeader(t, append([]byte("\x02\x04k1\x04v1"), morePositiveThanMaxBlockCount...), "cannot read map when block count exceeds") +} + +// readOCFHeader, avro.codec + +func TestReadOCFHeaderMetadataAvroCodecUnknown(t *testing.T) { + testCannotReadOCFHeader(t, []byte("\x02\x14avro.codec\x06bad\x00"), "cannot read OCF header", "unrecognized compression", "bad") +} + +// readOCFHeader, avro.schema + +func TestReadOCFHeaderMetadataAvroSchemaMissing(t *testing.T) { + testCannotReadOCFHeader(t, []byte("\x00"), "without avro.schema") + testCannotReadOCFHeader(t, []byte("\x02\x16avro.schema\x04{}\x00"), "invalid avro.schema") +} + +// readOCFHeader, sync marker + +func TestReadOCFHeaderMetadataSyncMarker(t *testing.T) { + testCannotReadOCFHeader(t, []byte("\x02\x16avro.schema\x1e{\"type\":\"null\"}\x00"), "sync marker", "EOF") +} + +// TODO: writeOCFHeader + +// +// OCFReader +// + +// func testOCFReader(t *testing.T, schema string, input []byte, expected ...string) { +// _, err := NewOCFReader(bytes.NewBuffer(append([]byte("Obj\x01"), input...))) +// ensureError(t, err, append([]string{"any prefix?"}, expected...)...) +// } + +// func TestOCFReaderRead(t *testing.T) { +// testOCFReader(t, +// } diff --git a/fork/goavro/ocf_test.go b/fork/goavro/ocf_test.go new file mode 100644 index 0000000..3d4d4bd --- /dev/null +++ b/fork/goavro/ocf_test.go @@ -0,0 +1,97 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "bytes" + "fmt" + "testing" +) + +// testOCFRoundTripWithHeaders has OCFWriter write to a buffer using specified +// compression algorithm, then attempt to read it back +func testOCFRoundTrip(t *testing.T, compressionName string) { + testOCFRoundTripWithHeaders(t, compressionName, nil) +} + +// testOCFRoundTripWithHeaders has OCFWriter write to a buffer using specified +// compression algorithm and headers, then attempt to read it back +func testOCFRoundTripWithHeaders(t *testing.T, compressionName string, headers map[string][]byte) { + schema := `{"type":"long"}` + + bb := new(bytes.Buffer) + ocfw, err := NewOCFWriter(OCFConfig{ + W: bb, + CompressionName: compressionName, + Schema: schema, + MetaData: headers, + }) + if err != nil { + t.Fatal(err) + } + + valuesToWrite := []int64{13, 42, -12, -1234} + + if err = ocfw.Append(valuesToWrite); err != nil { + t.Fatal(err) + } + + ocfr, err := NewOCFReader(bb) + if err != nil { + t.Fatal(err) + } + + var valuesRead []int64 + for ocfr.Scan() { + value, err := ocfr.Read() + if err != nil { + t.Fatal(err) + } + valuesRead = append(valuesRead, value.(int64)) + } + + if err = ocfr.Err(); err != nil { + t.Fatal(err) + } + + if actual, expected := len(valuesRead), len(valuesToWrite); actual != expected { + t.Errorf("GOT: %v; WANT: %v", actual, expected) + } + for i := 0; i < len(valuesRead); i++ { + if actual, expected := valuesRead[i], valuesToWrite[i]; actual != expected { + t.Errorf("GOT: %v; WANT: %v", actual, expected) + } + } + + readMeta := ocfr.MetaData() + for k, v := range headers { + expected := fmt.Sprintf("%s", v) + actual := fmt.Sprintf("%s", readMeta[k]) + if actual != expected { + t.Errorf("GOT: %v; WANT: %v (%v)", actual, expected, k) + } + } +} + +func TestOCFWriterCompressionNull(t *testing.T) { + testOCFRoundTrip(t, CompressionNullLabel) +} + +func TestOCFWriterCompressionDeflate(t *testing.T) { + testOCFRoundTrip(t, CompressionDeflateLabel) +} + +func TestOCFWriterCompressionSnappy(t *testing.T) { + testOCFRoundTrip(t, CompressionSnappyLabel) +} + +func TestOCFWriterWithApplicationMetaData(t *testing.T) { + testOCFRoundTripWithHeaders(t, CompressionNullLabel, map[string][]byte{"foo": []byte("BOING"), "goo": []byte("zoo")}) +} diff --git a/fork/goavro/ocf_writer.go b/fork/goavro/ocf_writer.go new file mode 100644 index 0000000..820af5c --- /dev/null +++ b/fork/goavro/ocf_writer.go @@ -0,0 +1,253 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "bytes" + "compress/flate" + "encoding/binary" + "errors" + "fmt" + "hash/crc32" + "io" + "io/ioutil" + "os" + + "github.com/golang/snappy" +) + +// OCFConfig is used to specify creation parameters for OCFWriter. +type OCFConfig struct { + // W specifies the `io.Writer` to which to send the encoded data, + // (required). If W is `*os.File`, then creating an OCF for writing will + // attempt to read any existing OCF header and use the schema and + // compression codec specified by the existing header, then advance the file + // position to the tail end of the file for appending. + W io.Writer + + // Codec specifies the Codec to use for the new OCFWriter, (optional). If + // the W parameter above is an `*os.File` which contains a Codec, the Codec + // in the existing file will be used instead. Otherwise if this Codec + // parameter is specified, it will be used. If neither the W parameter above + // is an `*os.File` with an existing Codec, nor this Codec parameter is + // specified, the OCFWriter will create a new Codec from the schema string + // specified by the Schema parameter below. + Codec *Codec + + // Schema specifies the Avro schema for the data to be encoded, (optional). + // If neither the W parameter above is an `*os.File` with an existing Codec, + // nor the Codec parameter above is specified, the OCFWriter will create a + // new Codec from the schema string specified by this Schema parameter. + Schema string + + // CompressionName specifies the compression codec used, (optional). If + // omitted, defaults to "null" codec. When appending to an existing OCF, + // this field is ignored. + CompressionName string + + //MetaData specifies application specific meta data to be added to + //the OCF file. When appending to an existing OCF, this field + //is ignored + MetaData map[string][]byte +} + +// OCFWriter is used to create a new or append to an existing Avro Object +// Container File (OCF). +type OCFWriter struct { + header *ocfHeader + iow io.Writer +} + +// NewOCFWriter returns a new OCFWriter instance that may be used for appending +// binary Avro data, either by appending to an existing OCF file or creating a +// new OCF file. +func NewOCFWriter(config OCFConfig) (*OCFWriter, error) { + var err error + ocf := &OCFWriter{iow: config.W} + + switch config.W.(type) { + case nil: + return nil, errors.New("cannot create OCFWriter when W is nil") + case *os.File: + file := config.W.(*os.File) + stat, err := file.Stat() + if err != nil { + return nil, fmt.Errorf("cannot create OCFWriter: %s", err) + } + // NOTE: When upstream provides a new file, it will already exist but + // have a size of 0 bytes. + if stat.Size() > 0 { + // attempt to read existing OCF header + if ocf.header, err = readOCFHeader(file); err != nil { + return nil, fmt.Errorf("cannot create OCFWriter: %s", err) + } + // prepare for appending data to existing OCF + if err = ocf.quickScanToTail(file); err != nil { + return nil, fmt.Errorf("cannot create OCFWriter: %s", err) + } + return ocf, nil // happy case for appending to existing OCF + } + } + + // create new OCF header based on configuration parameters + if ocf.header, err = newOCFHeader(config); err != nil { + return nil, fmt.Errorf("cannot create OCFWriter: %s", err) + } + if err = writeOCFHeader(ocf.header, config.W); err != nil { + return nil, fmt.Errorf("cannot create OCFWriter: %s", err) + } + return ocf, nil // another happy case for creation of new OCF +} + +// quickScanToTail advances the stream reader to the tail end of the +// file. Rather than reading each encoded block, optionally decompressing it, +// and then decoding it, this method reads the block count, ignoring it, then +// reads the block size, then skips ahead to the followig block. It does this +// repeatedly until attempts to read the file return io.EOF. +func (ocfw *OCFWriter) quickScanToTail(ior io.Reader) error { + sync := make([]byte, ocfSyncLength) + for { + // Read and validate block count + blockCount, err := longBinaryReader(ior) + if err != nil { + if err == io.EOF { + return nil // merely end of file, rather than error + } + return fmt.Errorf("cannot read block count: %s", err) + } + if blockCount <= 0 { + return fmt.Errorf("cannot read when block count is not greater than 0: %d", blockCount) + } + if blockCount > MaxBlockCount { + return fmt.Errorf("cannot read when block count exceeds MaxBlockCount: %d > %d", blockCount, MaxBlockCount) + } + // Read block size + blockSize, err := longBinaryReader(ior) + if err != nil { + return fmt.Errorf("cannot read block size: %s", err) + } + if blockSize <= 0 { + return fmt.Errorf("cannot read when block size is not greater than 0: %d", blockSize) + } + if blockSize > MaxBlockSize { + return fmt.Errorf("cannot read when block size exceeds MaxBlockSize: %d > %d", blockSize, MaxBlockSize) + } + // Advance reader to end of block + if _, err = io.CopyN(ioutil.Discard, ior, blockSize); err != nil { + return fmt.Errorf("cannot seek to next block: %s", err) + } + // Read and validate sync marker + var n int + if n, err = io.ReadFull(ior, sync); err != nil { + return fmt.Errorf("cannot read sync marker: read %d out of %d bytes: %s", n, ocfSyncLength, err) + } + if !bytes.Equal(sync, ocfw.header.syncMarker[:]) { + return fmt.Errorf("sync marker mismatch: %v != %v", sync, ocfw.header.syncMarker) + } + } +} + +// Append appends one or more data items to an OCF file in a block. If there are +// more data items in the slice than MaxBlockCount allows, the data slice will +// be chunked into multiple blocks, each not having more than MaxBlockCount +// items. +func (ocfw *OCFWriter) Append(data interface{}) error { + arrayValues, err := convertArray(data) + if err != nil { + return err + } + + // Chunk data so no block has more than MaxBlockCount items. + for int64(len(arrayValues)) > MaxBlockCount { + if err := ocfw.appendDataIntoBlock(arrayValues[:MaxBlockCount]); err != nil { + return err + } + arrayValues = arrayValues[MaxBlockCount:] + } + return ocfw.appendDataIntoBlock(arrayValues) +} + +func (ocfw *OCFWriter) appendDataIntoBlock(data []interface{}) error { + var block []byte // working buffer for encoding data values + var err error + + // Encode and concatenate each data item into the block + for _, datum := range data { + if block, err = ocfw.header.codec.BinaryFromNative(block, datum); err != nil { + return fmt.Errorf("cannot translate datum to binary: %v; %s", datum, err) + } + } + + switch ocfw.header.compressionID { + case compressionNull: + // no-op + + case compressionDeflate: + // compress into new bytes buffer. + bb := bytes.NewBuffer(make([]byte, 0, len(block))) + + cw, _ := flate.NewWriter(bb, flate.DefaultCompression) + // writing bytes to cw will compress bytes and send to bb. + if _, err := cw.Write(block); err != nil { + return err + } + if err := cw.Close(); err != nil { + return err + } + block = bb.Bytes() + + case compressionSnappy: + compressed := snappy.Encode(nil, block) + + // OCF requires snappy to have CRC32 checksum after each snappy block + compressed = append(compressed, 0, 0, 0, 0) // expand slice by 4 bytes so checksum will fit + binary.BigEndian.PutUint32(compressed[len(compressed)-4:], crc32.ChecksumIEEE(block)) // checksum of decompressed block + + block = compressed + + default: + return fmt.Errorf("should not get here: cannot compress block using unrecognized compression: %d", ocfw.header.compressionID) + + } + + // create file data block + buf := make([]byte, 0, len(block)+ocfBlockConst) // pre-allocate block bytes + buf, _ = longBinaryFromNative(buf, len(data)) // block count (number of data items) + buf, _ = longBinaryFromNative(buf, len(block)) // block size (number of bytes in block) + buf = append(buf, block...) // serialized objects + buf = append(buf, ocfw.header.syncMarker[:]...) // sync marker + + _, err = ocfw.iow.Write(buf) + return err +} + +// Codec returns the codec used by OCFWriter. This function provided because +// upstream may be appending to existing OCF which uses a different schema than +// requested during instantiation. +func (ocfw *OCFWriter) Codec() *Codec { + return ocfw.header.codec +} + +// CompressionName returns the name of the compression algorithm used by +// OCFWriter. This function provided because upstream may be appending to +// existing OCF which uses a different compression algorithm than requested +// during instantiation. the OCF file. +func (ocfw *OCFWriter) CompressionName() string { + switch ocfw.header.compressionID { + case compressionNull: + return CompressionNullLabel + case compressionDeflate: + return CompressionDeflateLabel + case compressionSnappy: + return CompressionSnappyLabel + default: + return "should not get here: unrecognized compression algorithm" + } +} diff --git a/fork/goavro/ocf_writer_test.go b/fork/goavro/ocf_writer_test.go new file mode 100644 index 0000000..1d334b4 --- /dev/null +++ b/fork/goavro/ocf_writer_test.go @@ -0,0 +1,297 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "bytes" + "io" + "os" + "testing" +) + +// createTestFile is used to create a new test file fixture with provided data +func createTestFile(t *testing.T, pathname string, data []byte) { + t.Helper() + nf, err := os.Create(pathname) + if err != nil { + t.Fatal(err) + } + if _, err = nf.Write(data); err != nil { + t.Fatal(err) + } + if err = nf.Close(); err != nil { + t.Fatal(err) + } +} + +// NOTE: already tested readOCFHeader + +func TestNewOCFWriterWhenNotFileNewOCFHeader(t *testing.T) { + // when config.W nil + _, err := NewOCFWriter(OCFConfig{}) + ensureError(t, err, "cannot create OCFWriter", "when W is nil") + + // when config.CompressionName invalid + _, err = NewOCFWriter(OCFConfig{W: new(bytes.Buffer), CompressionName: "*invalid*compression*algorithm*"}) + ensureError(t, err, "cannot create OCFWriter", "unrecognized compression algorithm") + + // when config.Schema doesn't compile + _, err = NewOCFWriter(OCFConfig{W: new(bytes.Buffer), CompressionName: "null", Schema: "invalid-schema"}) + ensureError(t, err, "cannot create OCFWriter", "cannot unmarshal schema") + + _, err = NewOCFWriter(OCFConfig{W: new(bytes.Buffer), CompressionName: "null", Schema: `{}`}) + ensureError(t, err, "cannot create OCFWriter", "missing type") + + _, err = NewOCFWriter(OCFConfig{W: new(bytes.Buffer), CompressionName: "null"}) + ensureError(t, err, "cannot create OCFWriter", "without either Codec or Schema specified") +} + +func TestNewOCFWriterWhenNotFileWriteOCFHeader(t *testing.T) { + _, err := NewOCFWriter(OCFConfig{ + W: ShortWriter(new(bytes.Buffer), 3), + CompressionName: "null", + Schema: `{"type":"int"}`}, + ) + ensureError(t, err, "cannot write OCF header", "short write") +} + +func TestNewOCFWriterWhenFileEmpty(t *testing.T) { + // NOTE: When given an empty file, NewOCFWriter ought to behave exactly as + // if it's merely given an non-file io.Writer. + fh, err := os.OpenFile("fixtures/temp0.avro", os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0666) + if err != nil { + t.Fatal(err) + } + _, err = NewOCFWriter(OCFConfig{ + W: fh, + CompressionName: "*invalid*", + Schema: `{"type":"int"}`}, + ) + ensureError(t, err, "cannot create OCFWriter", "unrecognized compression algorithm") +} + +func TestNewOCFWriterWhenFileNotEmptyWhenCannotReadOCFHeader(t *testing.T) { + fh, err := os.Open("fixtures/bad-header.avro") + if err != nil { + t.Fatal(err) + } + _, err = NewOCFWriter(OCFConfig{ + W: fh, + CompressionName: "*invalid*", + Schema: `{"type":"int"}`}, + ) + ensureError(t, err, "cannot create OCFWriter", "cannot read OCF header") +} + +func testNewOCFWriterWhenFile(t *testing.T, pathname string, expected ...string) { + t.Helper() + fh, err := os.Open(pathname) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := fh.Close(); err != nil { + t.Fatal(err) + } + }() + _, err = NewOCFWriter(OCFConfig{W: fh}) + ensureError(t, err, append([]string{"cannot create OCFWriter"}, expected...)...) +} + +func TestNewOCFWriterWhenFileNotEmptyWhenCannotQuickScanToTail(t *testing.T) { + testNewOCFWriterWhenFile(t, "fixtures/firstBlockCountNotGreaterThanZero.avro", "block count is not greater") + testNewOCFWriterWhenFile(t, "fixtures/blockCountExceedsMaxBlockCount.avro", "block count exceeds") + testNewOCFWriterWhenFile(t, "fixtures/cannotReadBlockSize.avro", "cannot read block size") + testNewOCFWriterWhenFile(t, "fixtures/blockSizeNotGreaterThanZero.avro", "block size is not greater than 0") + testNewOCFWriterWhenFile(t, "fixtures/blockSizeExceedsMaxBlockSize.avro", "block size exceeds") + testNewOCFWriterWhenFile(t, "fixtures/cannotDiscardBlockBytes.avro", "cannot seek to next block", "EOF") + testNewOCFWriterWhenFile(t, "fixtures/cannotReadSyncMarker.avro", "cannot read sync marker", "EOF") + testNewOCFWriterWhenFile(t, "fixtures/syncMarkerMismatch.avro", "sync marker mismatch") + testNewOCFWriterWhenFile(t, "fixtures/secondBlockCountZero.avro", "block count is not greater") +} + +func TestNewOCFWriterWhenFileNotEmptyWhenProvidedDifferentCompressionAndSchema(t *testing.T) { + createTestFile(t, "fixtures/temp1.avro", []byte("Obj\x01\x04\x14avro.codec\x0edeflate\x16avro.schema\x1e{\"type\":\"long\"}\x000123456789abcdef\x02\x04ab0123456789abcdef")) + fh, err := os.Open("fixtures/temp1.avro") + if err != nil { + t.Fatal(err) + } + defer func() { + if err := fh.Close(); err != nil { + t.Fatal(err) + } + }() + + ocfw, err := NewOCFWriter(OCFConfig{ + W: fh, + Schema: `{"type":"int"}`, + CompressionName: "null", + }) + if err != nil { + t.Fatal(err) + } + + if actual, expected := ocfw.Codec().Schema(), `{"type":"long"}`; actual != expected { + t.Errorf("GOT: %v; WANT: %v", actual, expected) + } + if actual, expected := ocfw.CompressionName(), CompressionDeflateLabel; actual != expected { + t.Errorf("GOT: %v; WANT: %v", actual, expected) + } +} + +func TestOCFWriterAppendWhenCannotWrite(t *testing.T) { + testPathname := "fixtures/temp2.avro" + createTestFile(t, testPathname, []byte("Obj\x01\x02\x16avro.schema\x1e{\"type\":\"long\"}\x000123456789abcdef")) + appender, err := os.OpenFile(testPathname, os.O_RDONLY, 0666) // open for read only will cause expected error when attempt to append + if err != nil { + t.Fatal(err) + } + defer func(ioc io.Closer) { + if err := ioc.Close(); err != nil { + t.Fatal(err) + } + }(appender) + + ocfw, err := NewOCFWriter(OCFConfig{W: appender}) + if err != nil { + t.Fatal(err) + } + + err = ocfw.Append([]interface{}{13, 42}) + ensureError(t, err, testPathname) +} + +func TestOCFWriterAppendSomeItemsToNothing(t *testing.T) { + testPathname := "fixtures/temp3.avro" + createTestFile(t, testPathname, []byte("Obj\x01\x02\x16avro.schema\x1e{\"type\":\"long\"}\x000123456789abcdef")) + appender, err := os.OpenFile(testPathname, os.O_RDWR, 0666) + if err != nil { + t.Fatal(err) + } + defer func(ioc io.Closer) { + if err := ioc.Close(); err != nil { + t.Fatal(err) + } + }(appender) + + ocfw, err := NewOCFWriter(OCFConfig{W: appender}) + if err != nil { + t.Fatal(err) + } + + if err = ocfw.Append([]interface{}{13, 42}); err != nil { + t.Fatal(err) + } + + // let's make sure data is there + reader, err := os.Open(testPathname) + if err != nil { + t.Fatal(err) + } + defer func(ioc io.Closer) { + if err := ioc.Close(); err != nil { + t.Fatal(err) + } + }(reader) + + ocfr, err := NewOCFReader(reader) + if err != nil { + t.Fatal(err) + } + + var values []int64 + for ocfr.Scan() { + value, err := ocfr.Read() + if err != nil { + t.Fatal(err) + } + values = append(values, value.(int64)) + } + if err := ocfr.Err(); err != nil { + t.Fatal(err) + } + + if actual, expected := len(values), 2; actual != expected { + t.Errorf("GOT: %v; WANT: %v", actual, expected) + } + if actual, expected := values[0], int64(13); actual != expected { + t.Errorf("GOT: %v; WANT: %v", actual, expected) + } + if actual, expected := values[1], int64(42); actual != expected { + t.Errorf("GOT: %v; WANT: %v", actual, expected) + } +} + +func TestOCFWriterAppendSomeItemsToSomeItems(t *testing.T) { + testPathname := "fixtures/temp4.avro" + createTestFile(t, testPathname, []byte("Obj\x01\x02\x16avro.schema\x1e{\"type\":\"long\"}\x000123456789abcdef\x04\x04\x1a\x540123456789abcdef")) + appender, err := os.OpenFile(testPathname, os.O_RDWR, 0666) + if err != nil { + t.Fatal(err) + } + defer func(ioc io.Closer) { + if err := ioc.Close(); err != nil { + t.Fatal(err) + } + }(appender) + + ocfw, err := NewOCFWriter(OCFConfig{W: appender}) + if err != nil { + t.Fatal(err) + } + + if err = ocfw.Append([]interface{}{-10, -100}); err != nil { + t.Fatal(err) + } + + // let's make sure data is there + reader, err := os.Open(testPathname) + if err != nil { + t.Fatal(err) + } + defer func(ioc io.Closer) { + if err := ioc.Close(); err != nil { + t.Fatal(err) + } + }(reader) + + ocfr, err := NewOCFReader(reader) + if err != nil { + t.Fatal(err) + } + + var values []int64 + for ocfr.Scan() { + value, err := ocfr.Read() + if err != nil { + t.Fatal(err) + } + values = append(values, value.(int64)) + } + if err := ocfr.Err(); err != nil { + t.Fatal(err) + } + + if actual, expected := len(values), 4; actual != expected { + t.Fatalf("GOT: %v; WANT: %v", actual, expected) + } + if actual, expected := values[0], int64(13); actual != expected { + t.Errorf("GOT: %v; WANT: %v", actual, expected) + } + if actual, expected := values[1], int64(42); actual != expected { + t.Errorf("GOT: %v; WANT: %v", actual, expected) + } + if actual, expected := values[2], int64(-10); actual != expected { + t.Errorf("GOT: %v; WANT: %v", actual, expected) + } + if actual, expected := values[3], int64(-100); actual != expected { + t.Errorf("GOT: %v; WANT: %v", actual, expected) + } +} diff --git a/fork/goavro/rabin.go b/fork/goavro/rabin.go new file mode 100644 index 0000000..bfb1bed --- /dev/null +++ b/fork/goavro/rabin.go @@ -0,0 +1,336 @@ +package goavro + +import ( + "encoding/binary" + "fmt" + "io" +) + +// rabinEmpty is a constant used to initialize the crc64Table, and to compute +// the CRC-64-AVRO fingerprint of every object schema. +const rabinEmpty = uint64(0xc15d213aa4d7a795) + +// rabinTable is never modified after initialization but its values are read to +// compute the CRC-64-AVRO fingerprint of every schema its given. +var rabinTable = [256]uint64{ + 0, + 3238593523956797946, + 6477187047913595892, + 8435907220062204430, + 12954374095827191784, + 11472609148414072338, + 16871814440124408860, + 14327483619285186022, + 16515860097293205755, + 14539261057490653441, + 13607494391182877455, + 10387063993012335349, + 6265406319754774291, + 8791864835633305321, + 1085550678754862311, + 2585467722461443357, + 5247393906202824413, + 7215812591205457703, + 1239030555549527337, + 4449591751341063379, + 18092457712352332085, + 15556728100436498639, + 11742789833002527425, + 10234164645493242683, + 12530812639509548582, + 9302088354573213660, + 17583729671266610642, + 15633189885995973672, + 2171101357509724622, + 3661574416647526452, + 5170935444922886714, + 7724537325157989312, + 10494787812405648826, + 13642865964979244096, + 14431625182410915406, + 16480541316673728436, + 2478061111099054674, + 1049933365183482792, + 8899183502682126758, + 6300970840149272668, + 8399466921467862337, + 6368420890995002555, + 3275086581351513781, + 108854135608684367, + 14364169659802000041, + 16980263386864569171, + 11435870349096892765, + 12845837170396948647, + 15669858317114364775, + 17692196227407282845, + 9265331945857609875, + 12422293323479818601, + 7688114635962061967, + 5062151678603773301, + 3698085083440658299, + 2279937883717887617, + 4342202715019449244, + 1203395666939462246, + 7323148833295052904, + 5282940851558637970, + 10341870889845773428, + 11778178981837571470, + 15449074650315978624, + 18057156506771531386, + 11669866394404287583, + 10160817855121008037, + 17874829710049597355, + 15339802717267265105, + 1311848476550706103, + 4523114428088083021, + 5464845951130112067, + 7432843562972398009, + 4956122222198109348, + 7509300761534850398, + 2099866730366965584, + 3591042414950500010, + 17798367005364253516, + 15848531969535615670, + 12601941680298545336, + 9372796311334617410, + 16798933842935724674, + 14253900473960229752, + 12736841781990005110, + 11255500115345754252, + 6550173162703027562, + 8509314479008689296, + 217708271217368734, + 3455596968422674276, + 870833084869474937, + 2370047569572014979, + 6194214610827729293, + 8721096401170761847, + 13822387873690697105, + 10602378625989962859, + 16587157392570359397, + 14609853536892473247, + 3483332339477899749, + 2064482512161650719, + 7616958077116566033, + 4991418462803860459, + 9480190278288059917, + 12637572737790640119, + 15741190762473065977, + 17762823925471730691, + 15376229271924123934, + 17983608511393921252, + 10124303357207546602, + 11561034798826117904, + 7396170166881316598, + 5356383260452470540, + 4559875767435775234, + 1420363961462201592, + 8684405430038898488, + 6085769495188764354, + 2406791333878924492, + 979366144819647798, + 14646297666590105808, + 16695918618875998506, + 10565881703117275940, + 13713538703073841886, + 11362911691697612739, + 12772455230081578553, + 14146576876296094775, + 16763373153642681805, + 3347869283551649835, + 182341662412566993, + 8616954185191982047, + 6585487012709290533, + 13933329357911598997, + 17126321439046432367, + 11006435164953838689, + 12992741788688209307, + 8257930048646602877, + 6803747195591438727, + 3132703159877387145, + 542775339377431155, + 2623696953101412206, + 619515277774763668, + 9046228856176166042, + 5871394916501263712, + 10929691902260224134, + 13501751302614184316, + 14865687125944796018, + 16338017159720129160, + 9912244444396218696, + 11925134239902742706, + 15018601523069700796, + 18202706530865158982, + 4199733460733931168, + 1637543290675756890, + 7182084829901000020, + 5717935174548446382, + 7834929158557182387, + 4632665972928804937, + 3844057317981030983, + 1849042541720329149, + 16103865201353027163, + 17549867708331900833, + 9700748483321744815, + 12280807109898935381, + 5834933197202143791, + 8937414855024798677, + 655924238275353051, + 2732422975565056033, + 16374796089197559239, + 14974255385173568573, + 13465025131935292979, + 10821211621719183305, + 13100346325406055124, + 11041713811386575662, + 17018628958017378592, + 13897997918303815898, + 435416542434737468, + 3097107305413864646, + 6911193936845348552, + 8293578696285179698, + 1741666169738949874, + 3808479038558283016, + 4740095139144029958, + 7870595381236532988, + 12388429221655458586, + 9736009554713699040, + 17442192802341523694, + 16068516186704462100, + 18239503069743100937, + 15127152172900050419, + 11888425678624364541, + 9803746554456753671, + 5681455845848806369, + 7073288438148047387, + 1673934641775824917, + 4308477092595991023, + 6966664678955799498, + 5503217582476919344, + 4128965024323301438, + 1566351579938693572, + 15233916154233132066, + 18417600011429070296, + 9982836925607720918, + 11996431537128302124, + 9627165335515697969, + 12207926510359495371, + 15886756170769674437, + 17332335396841578815, + 3917464579278591193, + 1922028658990515491, + 8051932600676513581, + 4850374241660872407, + 2917466598601071895, + 327962119137676525, + 8187398044598779619, + 6732512565967646489, + 11221777246008269567, + 13207379120439233285, + 14004037317153847563, + 17197450482186430705, + 14792340333762633196, + 16265093719173729302, + 10712766520904941080, + 13284123302255603682, + 9119751534871550468, + 5944212839312182270, + 2840727922924403184, + 836967320887912458, + 17368810860077796976, + 15995557527495450506, + 12171538990377528708, + 9518416773021940862, + 4813582667757848984, + 7943378085384837218, + 1958732289639295596, + 4025966300338256790, + 1458733299300535947, + 4093699022299389809, + 5610888623004134783, + 7002018658576923781, + 12103802978479819107, + 10018419036150929561, + 18310175810188503703, + 15198246066092718957, + 13391477134206599341, + 10748366240846565719, + 16157651908532642649, + 14756687855020634787, + 729366649650267973, + 2805444311502067391, + 6051901489239909553, + 9155087905094251851, + 6695738567103299670, + 8078825954266321324, + 364683324825133986, + 3025950744619954776, + 17233908370383964094, + 14112856248920397380, + 13170974025418581066, + 11113046258555286960, +} + +// rabin returns an unsigned 64-bit integer Rabin fingerprint for buf. NOTE: +// This is only used during Codec instantiation to calculate the Rabin +// fingerprint of the canonical schema. +func rabin(buf []byte) uint64 { + fp := rabinEmpty + for i := 0; i < len(buf); i++ { + fp = (fp >> 8) ^ rabinTable[(byte(fp)^buf[i])&0xff] // unsigned right shift >>> + } + return fp +} + +const soeMagicPrefix = 2 // 2-byte prefix for SOE encoded data +const soeHeaderLen = soeMagicPrefix + 8 // 2-byte prefix plus 8-byte fingerprint + +// FingerprintFromSOE returns the unsigned 64-bit Rabin fingerprint from the +// header of a buffer that encodes a Single-Object Encoded datum. This function +// is designed to be used to lookup a Codec that can decode the contents of the +// buffer. Once a Codec is found that has the matching Rabin fingerprint, its +// NativeFromBinary method may be used to decode the remaining bytes returned as +// the second return value. On failure this function returns an +// ErrNotSingleObjectEncoded error. +// +// func decode(codex map[uint64]*goavro.Codec, buf []byte) error { +// // Perform a sanity check on the buffer, then return the Rabin fingerprint +// // of the schema used to encode the data. +// fingerprint, newBuf, err := goavro.FingerprintFromSOE(buf) +// if err != nil { +// return err +// } +// +// // Get a previously stored Codec from the codex map. +// codec, ok := codex[fingerprint] +// if !ok { +// return fmt.Errorf("unknown codec: %#x", fingerprint) +// } +// +// // Use the fetched Codec to decode the buffer as a SOE. +// // +// // Faster because SOE magic prefix and schema fingerprint already +// // checked and used to fetch the Codec. Just need to decode the binary +// // bytes remaining after the prefix were removed. +// datum, _, err := codec.NativeFromBinary(newBuf) +// if err != nil { +// return err +// } +// +// _, err = fmt.Println(datum) +// return err +// } +func FingerprintFromSOE(buf []byte) (uint64, []byte, error) { + if len(buf) < soeHeaderLen { + // Not enough bytes to encode schema fingerprint. + return 0, nil, ErrNotSingleObjectEncoded(io.ErrShortBuffer.Error()) + } + + if buf[0] != 0xC3 || buf[1] != 0x01 { + // Currently only one SOE prefix is recognized. + return 0, nil, ErrNotSingleObjectEncoded(fmt.Sprintf("unknown SOE prefix: %#x", buf[:soeMagicPrefix])) + } + + // Only recognizes single-object encodings format version 1. + return binary.LittleEndian.Uint64(buf[soeMagicPrefix:]), buf[soeHeaderLen:], nil +} diff --git a/fork/goavro/rabin_test.go b/fork/goavro/rabin_test.go new file mode 100644 index 0000000..e6adb72 --- /dev/null +++ b/fork/goavro/rabin_test.go @@ -0,0 +1,19 @@ +package goavro + +import ( + "testing" +) + +func TestRabin(t *testing.T) { + t.Run("int", func(t *testing.T) { + if got, want := rabin([]byte(`"int"`)), uint64(0x7275d51a3f395c8f); got != want { + t.Errorf("GOT: %#x; WANT: %#x", got, want) + } + }) + + t.Run("string", func(t *testing.T) { + if got, want := rabin([]byte(`"string"`)), uint64(0x8f014872634503c7); got != want { + t.Errorf("GOT: %#x; WANT: %#x", got, want) + } + }) +} diff --git a/fork/goavro/race_test.go b/fork/goavro/race_test.go new file mode 100644 index 0000000..894558d --- /dev/null +++ b/fork/goavro/race_test.go @@ -0,0 +1,218 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "fmt" + "sync" + "testing" +) + +func TestRaceEncodeEncodeArray(t *testing.T) { + codec, err := NewCodec(`{"type":"record","name":"record1","fields":[{"name":"field1","type":"array","items":"long"}]}`) + if err != nil { + t.Fatal(err) + } + + var consumers, producers sync.WaitGroup + consumers.Add(1) + producers.Add(2) + + done := make(chan error, 10) + go func() { + defer consumers.Done() + for err := range done { + t.Error(err) + } + }() + + go func() { + defer producers.Done() + for i := 0; i < 10000; i++ { + if _, err := codec.BinaryFromNative(nil, map[string]interface{}{"field1": []int{i}}); err != nil { + done <- err + return + } + } + }() + + go func() { + defer producers.Done() + for i := 0; i < 10000; i++ { + rec := map[string]interface{}{ + "field1": []interface{}{i}, + } + if _, err := codec.BinaryFromNative(nil, rec); err != nil { + done <- err + return + } + } + }() + + producers.Wait() + close(done) + consumers.Wait() +} + +func TestRaceEncodeEncodeRecord(t *testing.T) { + codec, err := NewCodec(`{"type":"record","name":"record1","fields":[{"type":"long","name":"field1"}]}`) + if err != nil { + t.Fatal(err) + } + + var consumers, producers sync.WaitGroup + consumers.Add(1) + producers.Add(2) + + done := make(chan error, 10) + go func() { + defer consumers.Done() + for err := range done { + t.Error(err) + } + }() + + go func() { + defer producers.Done() + for i := 0; i < 10000; i++ { + rec := map[string]interface{}{"field1": i} + if _, err := codec.BinaryFromNative(nil, rec); err != nil { + done <- err + return + } + } + }() + + go func() { + defer producers.Done() + for i := 0; i < 10000; i++ { + rec := map[string]interface{}{"field1": i} + if _, err := codec.BinaryFromNative(nil, rec); err != nil { + done <- err + return + } + } + }() + + producers.Wait() + close(done) + consumers.Wait() +} + +func TestRaceCodecConstructionDecode(t *testing.T) { + codec, err := NewCodec(`{"type": "long"}`) + if err != nil { + t.Fatal(err) + } + comms := make(chan []byte, 1000) + + var consumers sync.WaitGroup + consumers.Add(1) + + done := make(chan error, 10) + go func() { + defer consumers.Done() + for err := range done { + t.Error(err) + } + }() + + go func() { + defer close(comms) + for i := 0; i < 10000; i++ { + // Completely unrelated stateful objects were causing races + if i%100 == 0 { + _, _ = NewCodec(`{"type": "long"}`) + } + buf, err := codec.BinaryFromNative(nil, i) + if err != nil { + done <- err + return + } + + comms <- buf + } + }() + + go func() { + defer close(done) + var i int64 + for buf := range comms { + datum, _, err := codec.NativeFromBinary(buf) + if err != nil { + done <- err + return + } + result := datum.(int64) // Avro long values always decoded as int64 + if result != i { + done <- fmt.Errorf("GOT: %v; WANT: %v", result, i) + return + } + i++ + } + }() + + consumers.Wait() +} + +func TestRaceCodecConstruction(t *testing.T) { + + comms := make(chan []byte, 1000) + done := make(chan error, 1000) + + go func() { + defer close(comms) + recordSchemaJSON := `{"type": "long"}` + codec, err := NewCodec(recordSchemaJSON) + if err != nil { + done <- err + return + } + + for i := 0; i < 10000; i++ { + buf, err := codec.BinaryFromNative(nil, i) + if err != nil { + done <- err + return + } + comms <- buf + } + }() + + go func() { + defer close(done) + recordSchemaJSON := `{"type": "long"}` + codec, err := NewCodec(recordSchemaJSON) + if err != nil { + done <- err + return + } + var i int64 + for encoded := range comms { + decoded, _, err := codec.NativeFromBinary(encoded) + if err != nil { + done <- err + return + } + result := decoded.(int64) // Avro long values always decoded as int64 + if result != i { + done <- fmt.Errorf("GOT: %v; WANT: %v", result, i) + return + } + i++ + } + }() + + for err := range done { + if err != nil { + t.Fatal(err) + } + } +} diff --git a/fork/goavro/record.go b/fork/goavro/record.go new file mode 100644 index 0000000..dd56058 --- /dev/null +++ b/fork/goavro/record.go @@ -0,0 +1,233 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "fmt" +) + +func makeRecordCodec(converters map[string]ConvertBuild, st map[string]*Codec, enclosingNamespace string, schemaMap map[string]interface{}, cb *codecBuilder) (*Codec, error) { + // NOTE: To support recursive data types, create the codec and register it + // using the specified name, and fill in the codec functions later. + c, err := registerNewNamedCodec(st, schemaMap, enclosingNamespace) + if err != nil { + return nil, fmt.Errorf("record ought to have valid name: %s", err) + } + + fields, ok := schemaMap["fields"] + if !ok { + return nil, fmt.Errorf("record %q ought to have fields key", c.typeName) + } + fieldSchemas, ok := fields.([]interface{}) + if !ok || fieldSchemas == nil { + return nil, fmt.Errorf("record %q fields ought to be non-nil array: %v", c.typeName, fields) + } + + codecFromFieldName := make(map[string]*Codec) + codecFromIndex := make([]*Codec, len(fieldSchemas)) + nameFromIndex := make([]string, len(fieldSchemas)) + defaultValueFromName := make(map[string]interface{}, len(fieldSchemas)) + + for i, fieldSchema := range fieldSchemas { + fieldSchemaMap, ok := fieldSchema.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("record %q field %d ought to be valid Avro named type; received: %v", c.typeName, i+1, fieldSchema) + } + + // NOTE: field names are not registered in the symbol table, because + // field names are not individually addressable codecs. + + fieldCodec, err := buildCodec(converters, st, c.typeName.namespace, fieldSchemaMap, cb) + if err != nil { + return nil, fmt.Errorf("record %q field %d ought to be valid Avro named type: %s", c.typeName, i+1, err) + } + + // However, when creating a full name for the field name, be sure to use + // record's namespace + n, err := newNameFromSchemaMap(c.typeName.namespace, fieldSchemaMap) + if err != nil { + return nil, fmt.Errorf("record %q field %d ought to have valid name: %v", c.typeName, i+1, fieldSchemaMap) + } + fieldName := n.short() + if _, ok := codecFromFieldName[fieldName]; ok { + return nil, fmt.Errorf("record %q field %d ought to have unique name: %q", c.typeName, i+1, fieldName) + } + + if defaultValue, ok := fieldSchemaMap["default"]; ok { + typeNameShort := fieldCodec.typeName.short() + switch typeNameShort { + case "boolean": + v, ok := defaultValue.(bool) + if !ok { + return nil, fmt.Errorf("record %q field %q: default value ought to encode using field schema: %s", c.typeName, fieldName, err) + } + defaultValue = bool(v) + case "bytes": + v, ok := defaultValue.(string) + if !ok { + return nil, fmt.Errorf("record %q field %q: default value ought to encode using field schema: %s", c.typeName, fieldName, err) + } + defaultValue = []byte(v) + case "double": + v, ok := defaultValue.(float64) + if !ok { + return nil, fmt.Errorf("record %q field %q: default value ought to encode using field schema: %s", c.typeName, fieldName, err) + } + defaultValue = float64(v) + case "float": + v, ok := defaultValue.(float64) + if !ok { + return nil, fmt.Errorf("record %q field %q: default value ought to encode using field schema: %s", c.typeName, fieldName, err) + } + defaultValue = float32(v) + case "int": + v, ok := defaultValue.(float64) + if !ok { + return nil, fmt.Errorf("record %q field %q: default value ought to encode using field schema: %s", c.typeName, fieldName, err) + } + defaultValue = int32(v) + case "long": + v, ok := defaultValue.(float64) + if !ok { + return nil, fmt.Errorf("record %q field %q: default value ought to encode using field schema: %s", c.typeName, fieldName, err) + } + defaultValue = int64(v) + case "string": + v, ok := defaultValue.(string) + if !ok { + return nil, fmt.Errorf("record %q field %q: default value ought to encode using field schema: %s", c.typeName, fieldName, err) + } + defaultValue = string(v) + case "union": + // When codec is union, then default value ought to encode using + // first schema in union. NOTE: To support a null default + // value, the string literal "null" must be coerced to a `nil` + if defaultValue == "null" { + defaultValue = nil + } + // NOTE: To support record field default values, union schema + // set to the type name of first member + // TODO: change to schemaCanonical below + defaultValue = Union(fieldCodec.schemaOriginal, defaultValue) + default: + debug("fieldName: %q; type: %q; defaultValue: %T(%#v)\n", fieldName, c.typeName, defaultValue, defaultValue) + } + + // attempt to encode default value using codec + _, err = fieldCodec.binaryFromNative(nil, defaultValue) + if err != nil { + return nil, fmt.Errorf("record %q field %q: default value ought to encode using field schema: %s", c.typeName, fieldName, err) + } + defaultValueFromName[fieldName] = defaultValue + } + + nameFromIndex[i] = fieldName + codecFromIndex[i] = fieldCodec + codecFromFieldName[fieldName] = fieldCodec + } + + c.binaryFromNative = func(buf []byte, datum interface{}) ([]byte, error) { + valueMap, ok := datum.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("cannot encode binary record %q: expected map[string]interface{}; received: %T", c.typeName, datum) + } + + // records encoded in order fields were defined in schema + for i, fieldCodec := range codecFromIndex { + fieldName := nameFromIndex[i] + + // NOTE: If field value was not specified in map, then set + // fieldValue to its default value (which may or may not have been + // specified). + fieldValue, ok := valueMap[fieldName] + if !ok { + if fieldValue, ok = defaultValueFromName[fieldName]; !ok { + return nil, fmt.Errorf("cannot encode binary record %q field %q: schema does not specify default value and no value provided", c.typeName, fieldName) + } + } + + var err error + buf, err = fieldCodec.binaryFromNative(buf, fieldValue) + if err != nil { + return nil, fmt.Errorf("cannot encode binary record %q field %q: value does not match its schema: %s", c.typeName, fieldName, err) + } + } + return buf, nil + } + + c.nativeFromBinary = func(buf []byte) (interface{}, []byte, error) { + recordMap := make(map[string]interface{}, len(codecFromIndex)) + for i, fieldCodec := range codecFromIndex { + name := nameFromIndex[i] + var value interface{} + var err error + value, buf, err = fieldCodec.nativeFromBinary(buf) + if err != nil { + return nil, nil, fmt.Errorf("cannot decode binary record %q field %q: %s", c.typeName, name, err) + } + recordMap[name] = value + } + return recordMap, buf, nil + } + + c.nativeFromTextual = func(buf []byte) (interface{}, []byte, error) { + var mapValues map[string]interface{} + var err error + // NOTE: Setting `defaultCodec == nil` instructs genericMapTextDecoder + // to return an error when a field name is not found in the + // codecFromFieldName map. + mapValues, buf, err = genericMapTextDecoder(buf, nil, codecFromFieldName) + if err != nil { + return nil, nil, fmt.Errorf("cannot decode textual record %q: %s", c.typeName, err) + } + if actual, expected := len(mapValues), len(codecFromFieldName); actual != expected { + // set missing field keys to their respective default values, then + // re-check number of keys + for fieldName, defaultValue := range defaultValueFromName { + if _, ok := mapValues[fieldName]; !ok { + mapValues[fieldName] = defaultValue + } + } + if actual, expected = len(mapValues), len(codecFromFieldName); actual != expected { + return nil, nil, fmt.Errorf("cannot decode textual record %q: only found %d of %d fields", c.typeName, actual, expected) + } + } + return mapValues, buf, nil + } + + c.textualFromNative = func(buf []byte, datum interface{}) ([]byte, error) { + // NOTE: Ensure only schema defined field names are encoded; and if + // missing in datum, either use the provided field default value or + // return an error. + sourceMap, ok := datum.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("cannot encode textual record %q: expected map[string]interface{}; received: %T", c.typeName, datum) + } + destMap := make(map[string]interface{}, len(codecFromIndex)) + for fieldName := range codecFromFieldName { + fieldValue, ok := sourceMap[fieldName] + if !ok { + defaultValue, ok := defaultValueFromName[fieldName] + if !ok { + return nil, fmt.Errorf("cannot encode textual record %q field %q: schema does not specify default value and no value provided", c.typeName, fieldName) + } + fieldValue = defaultValue + } + destMap[fieldName] = fieldValue + } + datum = destMap + // NOTE: Setting `defaultCodec == nil` instructs genericMapTextEncoder + // to return an error when a field name is not found in the + // codecFromFieldName map. + return genericMapTextEncoder(buf, datum, nil, codecFromFieldName) + } + + return c, nil +} diff --git a/fork/goavro/record_test.go b/fork/goavro/record_test.go new file mode 100644 index 0000000..21100d6 --- /dev/null +++ b/fork/goavro/record_test.go @@ -0,0 +1,719 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "bytes" + "fmt" + "testing" +) + +func TestRecordName(t *testing.T) { + testSchemaInvalid(t, `{"type":"record","fields":[{"name":"name","type":"string"},{"name":"age","type":"int"}]}`, "Record ought to have valid name: schema ought to have name key") + testSchemaInvalid(t, `{"type":"record","name":3}`, "Record ought to have valid name: schema name ought to be non-empty string") + testSchemaInvalid(t, `{"type":"record","name":""}`, "Record ought to have valid name: schema name ought to be non-empty string") + testSchemaInvalid(t, `{"type":"record","name":"&foo","fields":[{"name":"name","type":"string"},{"name":"age","type":"int"}]}`, "Record ought to have valid name: schema name ought to start with") + testSchemaInvalid(t, `{"type":"record","name":"foo&","fields":[{"name":"name","type":"string"},{"name":"age","type":"int"}]}`, "Record ought to have valid name: schema name ought to have second and remaining") +} + +func TestRecordFields(t *testing.T) { + testSchemaInvalid(t, `{"type":"record","name":"r1"}`, `Record "r1" ought to have fields key`) + testSchemaInvalid(t, `{"type":"record","name":"r1","fields":3}`, `Record "r1" fields ought to be non-nil array`) + testSchemaInvalid(t, `{"type":"record","name":"r1","fields":null}`, `Record "r1" fields ought to be non-nil array`) +} + +func TestRecordFieldInvalid(t *testing.T) { + testSchemaInvalid(t, `{"type":"record","name":"r1","fields":[3]}`, `Record "r1" field 1 ought to be valid Avro named type`) + testSchemaInvalid(t, `{"type":"record","name":"r1","fields":[""]}`, `Record "r1" field 1 ought to be valid Avro named type`) + testSchemaInvalid(t, `{"type":"record","name":"r1","fields":[{}]}`, `Record "r1" field 1 ought to be valid Avro named type`) + testSchemaInvalid(t, `{"type":"record","name":"r1","fields":[{"type":"int"}]}`, `Record "r1" field 1 ought to have valid name`) + testSchemaInvalid(t, `{"type":"record","name":"r1","fields":[{"name":"f1"}]}`, `Record "r1" field 1 ought to be valid Avro named type`) + testSchemaInvalid(t, `{"type":"record","name":"r1","fields":[{"name":"f1","type":"integer"}]}`, `Record "r1" field 1 ought to be valid Avro named type`) + testSchemaInvalid(t, `{"type":"record","name":"r1","fields":[{"name":"f1","type":"int"},{"name":"f1","type":"long"}]}`, `Record "r1" field 2 ought to have unique name`) +} + +func TestSchemaRecord(t *testing.T) { + testSchemaValid(t, `{ + "name": "person", + "type": "record", + "fields": [ + { + "name": "height", + "type": "long" + }, + { + "name": "weight", + "type": "long" + }, + { + "name": "name", + "type": "string" + } + ] +}`) +} + +func TestSchemaRecordFieldWithDefaults(t *testing.T) { + testSchemaValid(t, `{ + "name": "person", + "type": "record", + "fields": [ + { + "name": "height", + "type": "long" + }, + { + "name": "weight", + "type": "long" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "hacker", + "type": "boolean", + "default": false + } + ] +}`) +} + +func TestRecordDecodedEmptyBuffer(t *testing.T) { + testBinaryDecodeFailShortBuffer(t, `{"type":"record","name":"foo","fields":[{"name":"field1","type":"int"}]}`, nil) +} + +func TestRecordFieldTypeHasPrimitiveName(t *testing.T) { + codec, err := NewCodec(`{ + "type": "record", + "name": "r1", + "namespace": "com.example", + "fields": [ + { + "name": "f1", + "type": "string" + }, + { + "name": "f2", + "type": { + "type": "int" + } + } + ] +}`) + ensureError(t, err) + + datumIn := map[string]interface{}{ + "f1": "thirteen", + "f2": 13, + } + + buf, err := codec.BinaryFromNative(nil, datumIn) + ensureError(t, err) + if expected := []byte{ + 0x10, // field1 size = 8 + 't', 'h', 'i', 'r', 't', 'e', 'e', 'n', + 0x1a, // field2 == 13 + }; !bytes.Equal(buf, expected) { + t.Errorf("GOT: %#v; WANT: %#v", buf, expected) + } + + // round trip + datumOut, buf, err := codec.NativeFromBinary(buf) + ensureError(t, err) + if actual, expected := len(buf), 0; actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } + datumOutMap, ok := datumOut.(map[string]interface{}) + if !ok { + t.Errorf("GOT: %#v; WANT: %#v", ok, true) + } + if actual, expected := len(datumOutMap), len(datumIn); actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } + for k, v := range datumIn { + if actual, expected := fmt.Sprintf("%v", datumOutMap[k]), fmt.Sprintf("%v", v); actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } + } +} + +func TestSchemaRecordRecursive(t *testing.T) { + testSchemaValid(t, `{ + "type": "record", + "name": "recursive", + "fields": [ + { + "name": "label", + "type": "string" + }, + { + "name": "children", + "type": { + "type": "array", + "items": "recursive" + } + } + ] +}`) +} + +func TestSchemaNamespaceRecursive(t *testing.T) { + testSchemaValid(t, `{ + "type": "record", + "name": "Container", + "namespace": "namespace1", + "fields": [ + { + "name": "contained", + "type": { + "type": "record", + "name": "MutuallyRecursive", + "fields": [ + { + "name": "label", + "type": "string" + }, + { + "name": "children", + "type": { + "type": "array", + "items": { + "type": "record", + "name": "MutuallyRecursive", + "namespace": "namespace2", + "fields": [ + { + "name": "value", + "type": "int" + }, + { + "name": "children", + "type": { + "type": "array", + "items": "namespace1.MutuallyRecursive" + } + }, + { + "name": "morechildren", + "type": { + "type": "array", + "items": "MutuallyRecursive" + } + } + ] + } + } + }, + { + "name": "anotherchild", + "type": "namespace2.MutuallyRecursive" + } + ] + } + } + ] +}`) +} + +func TestSchemaRecordNamespaceComposite(t *testing.T) { + testSchemaValid(t, `{ + "type": "record", + "namespace": "x", + "name": "Y", + "fields": [ + { + "name": "e", + "type": { + "type": "record", + "name": "Z", + "fields": [ + { + "name": "f", + "type": "x.Z" + } + ] + } + } + ] +}`) +} + +func TestSchemaRecordNamespaceFullName(t *testing.T) { + testSchemaValid(t, `{ + "type": "record", + "name": "x.Y", + "fields": [ + { + "name": "e", + "type": { + "type": "record", + "name": "Z", + "fields": [ + { + "name": "f", + "type": "x.Y" + }, + { + "name": "g", + "type": "x.Z" + } + ] + } + } + ] +}`) +} + +func TestSchemaRecordNamespaceEnum(t *testing.T) { + testSchemaValid(t, `{"type": "record", "name": "org.apache.avro.tests.Hello", "fields": [ + {"name": "f1", "type": {"type": "enum", "name": "MyEnum", "symbols": ["Foo", "Bar", "Baz"]}}, + {"name": "f2", "type": "org.apache.avro.tests.MyEnum"}, + {"name": "f3", "type": "MyEnum"}, + {"name": "f4", "type": {"type": "enum", "name": "other.namespace.OtherEnum", "symbols": ["one", "two", "three"]}}, + {"name": "f5", "type": "other.namespace.OtherEnum"}, + {"name": "f6", "type": {"type": "enum", "name": "ThirdEnum", "namespace": "some.other", "symbols": ["Alice", "Bob"]}}, + {"name": "f7", "type": "some.other.ThirdEnum"} +]}`) +} + +func TestSchemaRecordNamespaceFixed(t *testing.T) { + testSchemaValid(t, `{"type": "record", "name": "org.apache.avro.tests.Hello", "fields": [ + {"name": "f1", "type": {"type": "fixed", "name": "MyFixed", "size": 16}}, + {"name": "f2", "type": "org.apache.avro.tests.MyFixed"}, + {"name": "f3", "type": "MyFixed"}, + {"name": "f4", "type": {"type": "fixed", "name": "other.namespace.OtherFixed", "size": 18}}, + {"name": "f5", "type": "other.namespace.OtherFixed"}, + {"name": "f6", "type": {"type": "fixed", "name": "ThirdFixed", "namespace": "some.other", "size": 20}}, + {"name": "f7", "type": "some.other.ThirdFixed"} +]}`) +} + +func TestRecordNamespace(t *testing.T) { + c, err := NewCodec(`{ + "type": "record", + "name": "org.foo.Y", + "fields": [ + { + "name": "X", + "type": { + "type": "fixed", + "size": 4, + "name": "fixed_4" + } + }, + { + "name": "Z", + "type": { + "type": "fixed_4" + } + } + ] +}`) + ensureError(t, err) + + datumIn := map[string]interface{}{ + "X": []byte("abcd"), + "Z": []byte("efgh"), + } + + buf, err := c.BinaryFromNative(nil, datumIn) + ensureError(t, err) + if expected := []byte("abcdefgh"); !bytes.Equal(buf, expected) { + t.Errorf("GOT: %#v; WANT: %#v", buf, expected) + } + + // round trip + datumOut, buf, err := c.NativeFromBinary(buf) + ensureError(t, err) + if actual, expected := len(buf), 0; actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } + datumOutMap, ok := datumOut.(map[string]interface{}) + if !ok { + t.Errorf("GOT: %#v; WANT: %#v", ok, true) + } + if actual, expected := len(datumOutMap), len(datumIn); actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } + for k, v := range datumIn { + if actual, expected := fmt.Sprintf("%s", datumOutMap[k]), fmt.Sprintf("%s", v); actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } + } +} + +func TestRecordEncodeFail(t *testing.T) { + schema := `{ + "type": "record", + "name": "r1", + "fields": [ + {"name": "f1", "type": "string"}, + {"name": "f2", "type": "string"} + ] +}` + + testBinaryEncodeFail(t, schema, map[string]interface{}{"f1": "foo"}, `field "f2": schema does not specify default value and no value provided`) + testBinaryEncodeFail(t, schema, map[string]interface{}{"f1": "foo", "f2": 13}, `field "f2": value does not match its schema`) +} + +func TestRecordTextDecodeFail(t *testing.T) { + schema := `{"name":"r1","type":"record","fields":[{"name":"string","type":"string"},{"name":"bytes","type":"bytes"}]}` + testTextDecodeFail(t, schema, []byte(` "string" : "silly" , "bytes" : "silly" } `), "expected: '{'") + testTextDecodeFail(t, schema, []byte(` { 16 : "silly" , "bytes" : "silly" } `), "expected initial \"") + testTextDecodeFail(t, schema, []byte(` { "badName" : "silly" , "bytes" : "silly" } `), "cannot determine codec") + testTextDecodeFail(t, schema, []byte(` { "string" , "silly" , "bytes" : "silly" } `), "expected: ':'") + testTextDecodeFail(t, schema, []byte(` { "string" : 13 , "bytes" : "silly" } `), "expected initial \"") + testTextDecodeFail(t, schema, []byte(` { "string" : "silly" : "bytes" : "silly" } `), "expected ',' or '}'") + testTextDecodeFail(t, schema, []byte(` { "string" : "silly" , "bytes" : "silly" `), "short buffer") + testTextDecodeFail(t, schema, []byte(` { "string" : "silly" `), "short buffer") + testTextDecodeFail(t, schema, []byte(` { "string" : "silly" } `), "only found 1 of 2 fields") +} + +func TestRecordTextCodecPass(t *testing.T) { + silly := "⌘ " + testTextEncodePass(t, `{"name":"r1","type":"record","fields":[{"name":"string","type":"string"}]}`, map[string]interface{}{"string": silly}, []byte(`{"string":"\u0001\u2318 "}`)) + testTextEncodePass(t, `{"name":"r1","type":"record","fields":[{"name":"bytes","type":"bytes"}]}`, map[string]interface{}{"bytes": []byte(silly)}, []byte(`{"bytes":"\u0001\u00E2\u008C\u0098 "}`)) + testTextDecodePass(t, `{"name":"r1","type":"record","fields":[{"name":"string","type":"string"},{"name":"bytes","type":"bytes"}]}`, map[string]interface{}{"string": silly, "bytes": []byte(silly)}, []byte(` { "string" : "\u0001\u2318 " , "bytes" : "\u0001\u00E2\u008C\u0098 " }`)) +} + +func TestRecordFieldDefaultValue(t *testing.T) { + testSchemaValid(t, `{"type":"record","name":"r1","fields":[{"name":"f1","type":"int","default":13}]}`) + testSchemaValid(t, `{"type":"record","name":"r1","fields":[{"name":"f1","type":"string","default":"foo"}]}`) + testSchemaInvalid(t, + `{"type":"record","name":"r1","fields":[{"name":"f1","type":"int","default":"foo"}]}`, + "default value ought to encode using field schema") +} + +func TestRecordFieldUnionDefaultValue(t *testing.T) { + testSchemaValid(t, `{"type":"record","name":"r1","fields":[{"name":"f1","type":["int","null"],"default":13}]}`) + testSchemaValid(t, `{"type":"record","name":"r1","fields":[{"name":"f1","type":["null","int"],"default":null}]}`) +} + +func TestRecordFieldUnionInvalidDefaultValue(t *testing.T) { + testSchemaInvalid(t, + `{"type":"record","name":"r1","fields":[{"name":"f1","type":["null","int"],"default":13}]}`, + "default value ought to encode using field schema") + testSchemaInvalid(t, + `{"type":"record","name":"r1","fields":[{"name":"f1","type":["int","null"],"default":null}]}`, + "default value ought to encode using field schema") +} + +func TestRecordRecursiveRoundTrip(t *testing.T) { + codec, err := NewCodec(` +{ + "type": "record", + "name": "LongList", + "fields" : [ + {"name": "next", "type": ["null", "LongList"], "default": null} + ] +} +`) + ensureError(t, err) + + // NOTE: May omit fields when using default value + initial := `{"next":{"LongList":{}}}` + + // NOTE: Textual encoding will show all fields, even those with values that + // match their default values + final := `{"next":{"LongList":{"next":null}}}` + + // Convert textual Avro data (in Avro JSON format) to native Go form + datum, _, err := codec.NativeFromTextual([]byte(initial)) + ensureError(t, err) + + // Convert native Go form to binary Avro data + buf, err := codec.BinaryFromNative(nil, datum) + ensureError(t, err) + + // Convert binary Avro data back to native Go form + datum, _, err = codec.NativeFromBinary(buf) + ensureError(t, err) + + // Convert native Go form to textual Avro data + buf, err = codec.TextualFromNative(nil, datum) + ensureError(t, err) + if actual, expected := string(buf), final; actual != expected { + t.Fatalf("GOT: %v; WANT: %v", actual, expected) + } +} + +func ExampleRecordRecursiveRoundTrip() { + codec, err := NewCodec(` +{ + "type": "record", + "name": "LongList", + "fields" : [ + {"name": "next", "type": ["null", "LongList"], "default": null} + ] +} +`) + if err != nil { + fmt.Println(err) + } + + // NOTE: May omit fields when using default value + textual := []byte(`{"next":{"LongList":{"next":{"LongList":{}}}}}`) + + // Convert textual Avro data (in Avro JSON format) to native Go form + native, _, err := codec.NativeFromTextual(textual) + if err != nil { + fmt.Println(err) + } + + // Convert native Go form to binary Avro data + binary, err := codec.BinaryFromNative(nil, native) + if err != nil { + fmt.Println(err) + } + + // Convert binary Avro data back to native Go form + native, _, err = codec.NativeFromBinary(binary) + if err != nil { + fmt.Println(err) + } + + // Convert native Go form to textual Avro data + textual, err = codec.TextualFromNative(nil, native) + if err != nil { + fmt.Println(err) + } + + // NOTE: Textual encoding will show all fields, even those with values that + // match their default values + fmt.Println(string(textual)) + // Output: {"next":{"LongList":{"next":{"LongList":{"next":null}}}}} +} + +func ExampleBinaryFromNative() { + codec, err := NewCodec(` +{ + "type": "record", + "name": "LongList", + "fields" : [ + {"name": "next", "type": ["null", "LongList"], "default": null} + ] +} +`) + if err != nil { + fmt.Println(err) + } + + // Convert native Go form to binary Avro data + binary, err := codec.BinaryFromNative(nil, map[string]interface{}{ + "next": map[string]interface{}{ + "LongList": map[string]interface{}{ + "next": map[string]interface{}{ + "LongList": map[string]interface{}{ + // NOTE: May omit fields when using default value + }, + }, + }, + }, + }) + if err != nil { + fmt.Println(err) + } + + fmt.Printf("%#v", binary) + // Output: []byte{0x2, 0x2, 0x0} +} + +func ExampleNativeFromBinary() { + codec, err := NewCodec(` +{ + "type": "record", + "name": "LongList", + "fields" : [ + {"name": "next", "type": ["null", "LongList"], "default": null} + ] +} +`) + if err != nil { + fmt.Println(err) + } + + // Convert native Go form to binary Avro data + binary := []byte{0x2, 0x2, 0x0} + + native, _, err := codec.NativeFromBinary(binary) + if err != nil { + fmt.Println(err) + } + + fmt.Printf("%v", native) + // Output: map[next:map[LongList:map[next:map[LongList:map[next:]]]]] +} + +func ExampleNativeFromTextual() { + codec, err := NewCodec(` +{ + "type": "record", + "name": "LongList", + "fields" : [ + {"name": "next", "type": ["null", "LongList"], "default": null} + ] +} +`) + if err != nil { + fmt.Println(err) + } + + // Convert native Go form to text Avro data + text := []byte(`{"next":{"LongList":{"next":{"LongList":{"next":null}}}}}`) + + native, _, err := codec.NativeFromTextual(text) + if err != nil { + fmt.Println(err) + } + + fmt.Printf("%v", native) + // Output: map[next:map[LongList:map[next:map[LongList:map[next:]]]]] +} + +func ExampleTextualFromNative() { + codec, err := NewCodec(` +{ + "type": "record", + "name": "LongList", + "fields" : [ + {"name": "next", "type": ["null", "LongList"], "default": null} + ] +} +`) + if err != nil { + fmt.Println(err) + } + + // Convert native Go form to text Avro data + text, err := codec.TextualFromNative(nil, map[string]interface{}{ + "next": map[string]interface{}{ + "LongList": map[string]interface{}{ + "next": map[string]interface{}{ + "LongList": map[string]interface{}{ + // NOTE: May omit fields when using default value + }, + }, + }, + }, + }) + if err != nil { + fmt.Println(err) + } + + fmt.Printf("%s", text) + // Output: {"next":{"LongList":{"next":{"LongList":{"next":null}}}}} +} + +func TestRecordFieldFixedDefaultValue(t *testing.T) { + testSchemaValid(t, `{"type": "record", "name": "r1", "fields":[{"name": "f1", "type": {"type": "fixed", "name": "someFixed", "size": 1}, "default": "\u0000"}]}`) +} + +func TestRecordFieldDefaultValueTypes(t *testing.T) { + t.Run("success", func(t *testing.T) { + codec, err := NewCodec(`{"type": "record", "name": "r1", "fields":[{"name": "someBoolean", "type": "boolean", "default": true},{"name": "someBytes", "type": "bytes", "default": "0"},{"name": "someDouble", "type": "double", "default": 0},{"name": "someFloat", "type": "float", "default": 0},{"name": "someInt", "type": "int", "default": 0},{"name": "someLong", "type": "long", "default": 0},{"name": "someString", "type": "string", "default": "0"}]}`) + ensureError(t, err) + + r1, _, err := codec.NativeFromTextual([]byte("{}")) + ensureError(t, err) + + r1m := r1.(map[string]interface{}) + + someBoolean := r1m["someBoolean"] + if _, ok := someBoolean.(bool); !ok { + t.Errorf("GOT: %T; WANT: []byte", someBoolean) + } + + someBytes := r1m["someBytes"] + if _, ok := someBytes.([]byte); !ok { + t.Errorf("GOT: %T; WANT: []byte", someBytes) + } + + someDouble := r1m["someDouble"] + if _, ok := someDouble.(float64); !ok { + t.Errorf("GOT: %T; WANT: float64", someDouble) + } + + someFloat := r1m["someFloat"] + if _, ok := someFloat.(float32); !ok { + t.Errorf("GOT: %T; WANT: float32", someFloat) + } + + someInt := r1m["someInt"] + if _, ok := someInt.(int32); !ok { + t.Errorf("GOT: %T; WANT: int32", someInt) + } + + someLong := r1m["someLong"] + if _, ok := someLong.(int64); !ok { + t.Errorf("GOT: %T; WANT: int64", someLong) + } + + someString := r1m["someString"] + if _, ok := someString.(string); !ok { + t.Errorf("GOT: %T; WANT: string", someString) + } + }) + + t.Run("provided default is wrong type", func(t *testing.T) { + t.Run("long", func(t *testing.T) { + _, err := NewCodec(`{"type": "record", "name": "r1", "fields":[{"name": "someLong", "type": "long", "default": "0"},{"name": "someInt", "type": "int", "default": 0},{"name": "someFloat", "type": "float", "default": 0},{"name": "someDouble", "type": "double", "default": 0}]}`) + ensureError(t, err, "field schema") + }) + t.Run("int", func(t *testing.T) { + _, err := NewCodec(`{"type": "record", "name": "r1", "fields":[{"name": "someLong", "type": "long", "default": 0},{"name": "someInt", "type": "int", "default": "0"},{"name": "someFloat", "type": "float", "default": 0},{"name": "someDouble", "type": "double", "default": 0}]}`) + ensureError(t, err, "field schema") + }) + t.Run("float", func(t *testing.T) { + _, err := NewCodec(`{"type": "record", "name": "r1", "fields":[{"name": "someLong", "type": "long", "default": 0},{"name": "someInt", "type": "int", "default": 0},{"name": "someFloat", "type": "float", "default": "0"},{"name": "someDouble", "type": "double", "default": 0}]}`) + ensureError(t, err, "field schema") + }) + t.Run("double", func(t *testing.T) { + _, err := NewCodec(`{"type": "record", "name": "r1", "fields":[{"name": "someLong", "type": "long", "default": 0},{"name": "someInt", "type": "int", "default": 0},{"name": "someFloat", "type": "float", "default": 0},{"name": "someDouble", "type": "double", "default": "0"}]}`) + ensureError(t, err, "field schema") + }) + }) + + t.Run("union of int and long", func(t *testing.T) { + t.Skip("FIXME: should encode default value as int64 rather than float64") + + codec, err := NewCodec(`{"type":"record","name":"r1","fields":[{"name":"f1","type":["int","long"],"default":13}]}`) + ensureError(t, err) + + r1, _, err := codec.NativeFromTextual([]byte("{}")) + ensureError(t, err) + + r1m := r1.(map[string]interface{}) + + someUnion := r1m["f1"] + someMap, ok := someUnion.(map[string]interface{}) + if !ok { + t.Fatalf("GOT: %T; WANT: map[string]interface{}", someUnion) + } + if got, want := len(someMap), 1; got != want { + t.Errorf("GOT: %v; WANT: %v", got, want) + } + t.Logf("someMap: %#v", someMap) + for k, v := range someMap { + // The "int" type is the first type option of the union. + if got, want := k, "int"; got != want { + t.Errorf("GOT: %v; WANT: %v", got, want) + } + switch tv := v.(type) { + case int64: + if got, want := tv, int64(13); got != want { + t.Errorf("GOT: %v; WANT: %v", got, want) + } + default: + t.Errorf("GOT: %T; WANT: int64", v) + } + } + }) +} diff --git a/fork/goavro/schema_test.go b/fork/goavro/schema_test.go new file mode 100644 index 0000000..2215312 --- /dev/null +++ b/fork/goavro/schema_test.go @@ -0,0 +1,271 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "fmt" + "testing" +) + +func testSchemaPrimativeCodec(t *testing.T, primitiveTypeName string) { + t.Helper() + if _, err := NewCodec(primitiveTypeName); err != nil { + t.Errorf("Bare primitive type: Schema: %q; Actual: %#v; Expected: %#v", primitiveTypeName, err, nil) + } + full := fmt.Sprintf(`{"type":%s}`, primitiveTypeName) + if _, err := NewCodec(full); err != nil { + t.Errorf("Full primitive type: Schema: %q; Actual: %#v; Expected: %#v", full, err, nil) + } + extra := fmt.Sprintf(`{"type":%s,"ignoredKey":"ignoredValue"}`, primitiveTypeName) + if _, err := NewCodec(extra); err != nil { + t.Errorf("Full primitive type with extra attributes: Schema: %q; Actual: %#v; Expected: %#v", extra, err, nil) + } +} + +func testSchemaInvalid(t *testing.T, schema, errorMessage string) { + t.Helper() + _, err := NewCodec(schema) + ensureError(t, err, errorMessage) +} + +func testSchemaValid(t *testing.T, schema string) { + t.Helper() + _, err := NewCodec(schema) + if err != nil { + t.Errorf("GOT: %v; WANT: %v", err, nil) + } +} + +func TestSchemaFailInvalidType(t *testing.T) { + testSchemaInvalid(t, `{"type":"flubber"}`, "unknown type name") +} + +func TestSchemaWeather(t *testing.T) { + testSchemaValid(t, ` +{"type": "record", "name": "test.Weather", + "doc": "A weather reading.", + "fields": [ + {"name": "station", "type": "string", "order": "ignore"}, + {"name": "time", "type": "long"}, + {"name": "temp", "type": "int"} + ] +} +`) +} + +func TestSchemaFooBarSpecificRecord(t *testing.T) { + testSchemaValid(t, ` +{ + "type": "record", + "name": "FooBarSpecificRecord", + "namespace": "org.apache.avro", + "fields": [ + {"name": "id", "type": "int"}, + {"name": "name", "type": "string"}, + {"name": "nicknames", "type": + {"type": "array", "items": "string"}}, + {"name": "relatedids", "type": + {"type": "array", "items": "int"}}, + {"name": "typeEnum", "type": + ["null", { + "type": "enum", + "name": "TypeEnum", + "namespace": "org.apache.avro", + "symbols" : ["a","b", "c"] + }], + "default": null + } + ] +} +`) +} + +func TestSchemaInterop(t *testing.T) { + testSchemaValid(t, ` +{"type": "record", "name":"Interop", "namespace": "org.apache.avro", + "fields": [ + {"name": "intField", "type": "int"}, + {"name": "longField", "type": "long"}, + {"name": "stringField", "type": "string"}, + {"name": "boolField", "type": "boolean"}, + {"name": "floatField", "type": "float"}, + {"name": "doubleField", "type": "double"}, + {"name": "bytesField", "type": "bytes"}, + {"name": "nullField", "type": "null"}, + {"name": "arrayField", "type": {"type": "array", "items": "double"}}, + {"name": "mapField", "type": + {"type": "map", "values": + {"type": "record", "name": "Foo", + "fields": [{"name": "label", "type": "string"}]}}}, + {"name": "unionField", "type": + ["boolean", "double", {"type": "array", "items": "bytes"}]}, + {"name": "enumField", "type": + {"type": "enum", "name": "Kind", "symbols": ["A","B","C"]}}, + {"name": "fixedField", "type": + {"type": "fixed", "name": "MD5", "size": 16}}, + {"name": "recordField", "type": + {"type": "record", "name": "Node", + "fields": [ + {"name": "label", "type": "string"}, + {"name": "children", "type": {"type": "array", "items": "Node"}}]}} + ] +} +`) +} + +func TestSchemaFixedNameCanBeUsedLater(t *testing.T) { + schema := `{"type":"record","name":"record1","fields":[ + {"name":"field1","type":{"type":"fixed","name":"fixed_4","size":4}}, + {"name":"field2","type":"fixed_4"}]}` + + datum := map[string]interface{}{ + "field1": []byte("abcd"), + "field2": []byte("efgh"), + } + + testBinaryEncodePass(t, schema, datum, []byte("abcdefgh")) +} + +// func ExampleCodecSchema() { +// schema := `{"type":"map","values":{"type":"enum","name":"foo","symbols":["alpha","bravo"]}}` +// codec, err := NewCodec(schema) +// if err != nil { +// fmt.Println(err) +// } +// fmt.Println(codec.Schema()) +// // Output: {"type":"map","values":{"name":"foo","type":"enum","symbols":["alpha","bravo"]}} +// } + +func TestMapValueTypeEnum(t *testing.T) { + schema := `{"type":"map","values":{"type":"enum","name":"foo","symbols":["alpha","bravo"]}}` + + datum := map[string]interface{}{"someKey": "bravo"} + + expected := []byte{ + 0x2, // blockCount = 1 pair + 0xe, // key size = 7 + 's', 'o', 'm', 'e', 'K', 'e', 'y', + 0x2, // value = index 1 ("bravo") + 0, // blockCount = 0 pairs + } + + testBinaryCodecPass(t, schema, datum, expected) +} + +func TestMapValueTypeRecord(t *testing.T) { + schema := `{"type":"map","values":{"type":"record","name":"foo","fields":[{"name":"field1","type":"string"},{"name":"field2","type":"int"}]}}` + + datum := map[string]interface{}{ + "map-key": map[string]interface{}{ + "field1": "unlucky", + "field2": 13, + }, + } + + expected := []byte{ + 0x2, // blockCount = 1 key-value pair in top level map + 0xe, // first key size = 7 + 'm', 'a', 'p', '-', 'k', 'e', 'y', // first key = "map-key" + // this key's value is a record, which is encoded by concatenated its field values + 0x0e, // field one string size = 7 + 'u', 'n', 'l', 'u', 'c', 'k', 'y', + 0x1a, // 13 + 0, // map has no more blocks + } + + // cannot decode because order of map key enumeration random, and records + // are returned as a Go map + testBinaryEncodePass(t, schema, datum, expected) +} + +func TestDefaultValueOughtToEncodeUsingFieldSchemaOK(t *testing.T) { + + testSchemaValid(t, ` + { + "namespace": "universe.of.things", + "type": "record", + "name": "Thing", + "fields": [ + { + "name": "attributes", + "type": [ + "null", + { + "type": "array", + "items": { + "namespace": "universe.of.things", + "type": "record", + "name": "attribute", + "fields": [ + { + "name": "name", + "type": "string" + }, + { + "name": "value", + "type": "string" + } + ] + } + } + ], + "default": "null" + } + ] + }`) + +} + +func TestUnionOfRecordsDefaultValueOughtToEncodeUsingFieldSchemaOK(t *testing.T) { + + testSchemaValid(t, ` + { + "type": "record", + "name": "Thing", + "namespace": "universe.of.things", + "fields": [ + { + "name": "layout", + "type": [ + { + "type": "record", + "name": "AnotherThing", + "namespace": "another.universe.of.things", + "fields": [ + { + "name": "text", + "type": "string", + "default": "someText" + } + ] + }, + { + "type": "record", + "name": "AnotherThing2", + "namespace": "another.universe.of.things", + "fields": [ + { + "name": "text", + "type": "string", + "default": "someOtherText" + } + ] + } + ], + "default": { + "another.universe.of.things.AnotherThing": { + "text": "someDefaultText" + } + } + } + ] + }`) + +} diff --git a/fork/goavro/text.go b/fork/goavro/text.go new file mode 100644 index 0000000..10e5308 --- /dev/null +++ b/fork/goavro/text.go @@ -0,0 +1,41 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "fmt" + "io" + "unicode" +) + +// advanceAndConsume advances to non whitespace and returns an error if the next +// non whitespace byte is not what is expected. +func advanceAndConsume(buf []byte, expected byte) ([]byte, error) { + var err error + if buf, err = advanceToNonWhitespace(buf); err != nil { + return nil, err + } + if actual := buf[0]; actual != expected { + return nil, fmt.Errorf("expected: %q; actual: %q", expected, actual) + } + return buf[1:], nil +} + +// advanceToNonWhitespace consumes bytes from buf until non-whitespace character +// is found. It returns error when no more bytes remain, because its purpose is +// to scan ahead to the next non-whitespace character. +func advanceToNonWhitespace(buf []byte) ([]byte, error) { + for i, b := range buf { + if !unicode.IsSpace(rune(b)) { + return buf[i:], nil + } + } + return nil, io.ErrShortBuffer +} diff --git a/fork/goavro/text_test.go b/fork/goavro/text_test.go new file mode 100644 index 0000000..f4895e9 --- /dev/null +++ b/fork/goavro/text_test.go @@ -0,0 +1,257 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "bytes" + "fmt" + "math" + "testing" +) + +func testTextDecodeFail(t *testing.T, schema string, buf []byte, errorMessage string) { + t.Helper() + c, err := NewCodec(schema) + if err != nil { + t.Fatal(err) + } + value, newBuffer, err := c.NativeFromTextual(buf) + ensureError(t, err, errorMessage) + if value != nil { + t.Errorf("GOT: %v; WANT: %v", value, nil) + } + if !bytes.Equal(buf, newBuffer) { + t.Errorf("GOT: %v; WANT: %v", newBuffer, buf) + } +} + +func testTextEncodeFail(t *testing.T, schema string, datum interface{}, errorMessage string) { + t.Helper() + c, err := NewCodec(schema) + if err != nil { + t.Fatal(err) + } + buf, err := c.TextualFromNative(nil, datum) + ensureError(t, err, errorMessage) + if buf != nil { + t.Errorf("GOT: %v; WANT: %v", buf, nil) + } +} + +func testTextEncodeFailBadDatumType(t *testing.T, schema string, datum interface{}) { + t.Helper() + testTextEncodeFail(t, schema, datum, "received: ") +} + +func testTextDecodeFailShortBuffer(t *testing.T, schema string, buf []byte) { + t.Helper() + testTextDecodeFail(t, schema, buf, "short buffer") +} + +func testTextDecodePass(t *testing.T, schema string, datum interface{}, encoded []byte) { + t.Helper() + codec, err := NewCodec(schema) + if err != nil { + t.Fatalf("schema: %s; %s", schema, err) + } + toNativeAndCompare(t, schema, datum, encoded, codec) +} +func testJSONDecodePass(t *testing.T, schema string, datum interface{}, encoded []byte) { + t.Helper() + codec, err := NewCodecFrom(schema, &codecBuilder{ + buildCodecForTypeDescribedByMap, + buildCodecForTypeDescribedByString, + buildCodecForTypeDescribedBySliceJSON, + }, nil) + if err != nil { + t.Fatalf("schema: %s; %s", schema, err) + } + toNativeAndCompare(t, schema, datum, encoded, codec) +} +func toNativeAndCompare(t *testing.T, schema string, datum interface{}, encoded []byte, codec *Codec) { + t.Helper() + decoded, remaining, err := codec.NativeFromTextual(encoded) + if err != nil { + t.Fatalf("schema: %s; %s", schema, err) + } + + // remaining ought to be empty because there is nothing remaining to be + // decoded + if actual, expected := len(remaining), 0; actual != expected { + t.Errorf("schema: %s; Datum: %#v; Actual: %v; Expected: %v", schema, datum, actual, expected) + } + + const ( + _ = iota + isInt = iota + isFloat32 = iota + isFloat64 = iota + isMap = iota + isSlice = iota + isString = iota + ) + + var datumType int + var datumInt int64 + var datumFloat32 float32 + var datumFloat64 float64 + var datumMap map[string]interface{} + var datumSlice []interface{} + var datumString string + switch v := datum.(type) { + case float64: + datumFloat64 = v + datumType = isFloat64 + case float32: + datumFloat32 = v + datumType = isFloat32 + case int: + datumInt = int64(v) + datumType = isInt + case int32: + datumInt = int64(v) + datumType = isInt + case int64: + datumInt = v + datumType = isInt + case string: + datumString = v + datumType = isString + case []interface{}: + datumSlice = v + datumType = isSlice + case map[string]interface{}: + datumMap = v + datumType = isMap + } + + var decodedType int + var decodedInt int64 + var decodedFloat32 float32 + var decodedFloat64 float64 + var decodedMap map[string]interface{} + var decodedSlice []interface{} + var decodedString string + switch v := decoded.(type) { + case float64: + decodedFloat64 = v + decodedType = isFloat64 + case float32: + decodedFloat32 = v + decodedType = isFloat32 + case int: + decodedInt = int64(v) + decodedType = isInt + case int32: + decodedInt = int64(v) + decodedType = isInt + case int64: + decodedInt = v + decodedType = isInt + case string: + decodedString = v + decodedType = isString + case []interface{}: + decodedSlice = v + decodedType = isSlice + case map[string]interface{}: + decodedMap = v + decodedType = isMap + } + + if datumType == isInt && decodedType == isInt { + if datumInt != decodedInt { + t.Errorf("numerical comparison: schema: %s; Datum: %v; Actual: %v; Expected: %v", schema, datum, decodedInt, datumInt) + } + return + } + // NOTE: Special handling when both datum and decoded values are floating + // point to test whether both are NaN, -Inf, or +Inf. + if datumType == isFloat64 && decodedType == isFloat64 { + if !(math.IsNaN(datumFloat64) && math.IsNaN(decodedFloat64)) && + !(math.IsInf(datumFloat64, 1) && math.IsInf(decodedFloat64, 1)) && + !(math.IsInf(datumFloat64, -1) && math.IsInf(decodedFloat64, -1)) && + datumFloat64 != decodedFloat64 { + t.Errorf("numerical comparison: schema: %s; Datum: %v; Actual: %v; Expected: %v", schema, datum, decodedFloat64, datumFloat64) + } + return + } + if datumType == isFloat32 && decodedType == isFloat32 { + a := float64(datumFloat32) + b := float64(decodedFloat32) + if !(math.IsNaN(a) && math.IsNaN(b)) && + !(math.IsInf(a, 1) && math.IsInf(b, 1)) && + !(math.IsInf(a, -1) && math.IsInf(b, -1)) && + a != b { + t.Errorf("numerical comparison: schema: %s; Datum: %v; Actual: %v; Expected: %v", schema, datum, decodedFloat32, datumFloat32) + } + return + } + if datumType == isMap && decodedType == isMap { + if actual, expected := len(decodedMap), len(datumMap); actual != expected { + t.Fatalf("map comparison: length mismatch; Actual: %v; Expected: %v", actual, expected) + } + for key, datumValue := range datumMap { + decodedValue, ok := decodedMap[key] + if !ok { + t.Fatalf("map comparison: decoded missing key: %q: Actual: %v; Expected: %v", key, decodedMap, datumMap) + } + if actual, expected := fmt.Sprintf("%v", decodedValue), fmt.Sprintf("%v", datumValue); actual != expected { + t.Errorf("map comparison: values differ for key: %q; Actual: %v; Expected: %v", key, actual, expected) + } + } + return + } + if datumType == isSlice && decodedType == isSlice { + if actual, expected := len(decodedMap), len(datumMap); actual != expected { + t.Fatalf("slice comparison: length mismatch; Actual: %v; Expected: %v", actual, expected) + } + for i, datumValue := range datumSlice { + decodedValue := decodedSlice[i] + if actual, expected := fmt.Sprintf("%v", decodedValue), fmt.Sprintf("%v", datumValue); actual != expected { + t.Errorf("slice comparison: values differ for index: %d: Actual: %v; Expected: %v", i+1, actual, expected) + } + } + return + } + if datumType == isString && decodedType == isString { + if actual, expected := decodedString, datumString; actual != expected { + t.Errorf("string comparison: Actual: %v; Expected: %v", actual, expected) + } + return + } + if actual, expected := fmt.Sprintf("%v", decoded), fmt.Sprintf("%v", datum); actual != expected { + t.Errorf("schema: %s; Datum: %v; Actual: %s; Expected: %s", schema, datum, actual, expected) + } +} + +func testTextEncodePass(t *testing.T, schema string, datum interface{}, expected []byte) { + t.Helper() + codec, err := NewCodec(schema) + if err != nil { + t.Fatalf("Schma: %q %s", schema, err) + } + + actual, err := codec.TextualFromNative(nil, datum) + if err != nil { + t.Fatalf("schema: %s; Datum: %v; %s", schema, datum, err) + } + if !bytes.Equal(actual, expected) { + t.Errorf("schema: %s; Datum: %v; Actual: %+q; Expected: %+q", schema, datum, actual, expected) + } +} + +// testTextCodecPass does a bi-directional codec check, by encoding datum to +// bytes, then decoding bytes back to datum. +func testTextCodecPass(t *testing.T, schema string, datum interface{}, buf []byte) { + t.Helper() + testTextDecodePass(t, schema, datum, buf) + testTextEncodePass(t, schema, datum, buf) +} diff --git a/fork/goavro/union.go b/fork/goavro/union.go new file mode 100644 index 0000000..25c552b --- /dev/null +++ b/fork/goavro/union.go @@ -0,0 +1,409 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "sort" +) + +// codecInfo is a set of quick lookups it holds all the lookup info for the +// all the schemas we need to handle the list of types for this union +type codecInfo struct { + allowedTypes []string + codecFromIndex []*Codec + codecFromName map[string]*Codec + indexFromName map[string]int +} + +func (cr *codecInfo) nullable() bool { + if len(cr.allowedTypes) == 2 { + _, found := cr.indexFromName["null"] + return found + } + return false +} + +// Union wraps a datum value in a map for encoding as a Union, as required by +// Union encoder. +// +// When providing a value for an Avro union, the encoder will accept `nil` for a +// `null` value. If the value is non-`nil`, it must be a +// `map[string]interface{}` with a single key-value pair, where the key is the +// Avro type name and the value is the datum's value. As a convenience, the +// `Union` function wraps any datum value in a map as specified above. +// +// func ExampleUnion() { +// codec, err := goavro.NewCodec(`["null","string","int"]`) +// if err != nil { +// fmt.Println(err) +// } +// buf, err := codec.TextualFromNative(nil, goavro.Union("string", "some string")) +// if err != nil { +// fmt.Println(err) +// } +// fmt.Println(string(buf)) +// // Output: {"string":"some string"} +// } +func Union(name string, datum interface{}) interface{} { + if datum == nil && name == "null" { + return nil + } + return map[string]interface{}{name: datum} +} + +// makeCodecInfo takes the schema array +// and builds some lookup indices +// returning a codecInfo +func makeCodecInfo(converters map[string]ConvertBuild, st map[string]*Codec, enclosingNamespace string, schemaArray []interface{}, cb *codecBuilder) (codecInfo, error) { + allowedTypes := make([]string, len(schemaArray)) // used for error reporting when encoder receives invalid datum type + codecFromIndex := make([]*Codec, len(schemaArray)) + codecFromName := make(map[string]*Codec, len(schemaArray)) + indexFromName := make(map[string]int, len(schemaArray)) + + for i, unionMemberSchema := range schemaArray { + unionMemberCodec, err := buildCodec(converters, st, enclosingNamespace, unionMemberSchema, cb) + if err != nil { + return codecInfo{}, fmt.Errorf("Union item %d ought to be valid Avro type: %s", i+1, err) + } + fullName := unionMemberCodec.typeName.fullName + if _, ok := indexFromName[fullName]; ok { + return codecInfo{}, fmt.Errorf("Union item %d ought to be unique type: %s", i+1, unionMemberCodec.typeName) + } + allowedTypes[i] = fullName + codecFromIndex[i] = unionMemberCodec + codecFromName[fullName] = unionMemberCodec + indexFromName[fullName] = i + } + + return codecInfo{ + allowedTypes: allowedTypes, + codecFromIndex: codecFromIndex, + codecFromName: codecFromName, + indexFromName: indexFromName, + }, nil + +} + +func nativeFromBinary(cr *codecInfo) func(buf []byte) (interface{}, []byte, error) { + + return func(buf []byte) (interface{}, []byte, error) { + var decoded interface{} + var err error + + decoded, buf, err = longNativeFromBinary(buf) + if err != nil { + return nil, nil, err + } + index := decoded.(int64) // longDecoder always returns int64, so elide error checking + if index < 0 || index >= int64(len(cr.codecFromIndex)) { + return nil, nil, fmt.Errorf("cannot decode binary union: index ought to be between 0 and %d; read index: %d", len(cr.codecFromIndex)-1, index) + } + c := cr.codecFromIndex[index] + decoded, buf, err = c.nativeFromBinary(buf) + if err != nil { + return nil, nil, fmt.Errorf("cannot decode binary union item %d: %s", index+1, err) + } + if decoded == nil { + // do not wrap a nil value in a map + return nil, buf, nil + } + // Non-nil values are wrapped in a map with single key set to type name of value + return Union(cr.allowedTypes[index], decoded), buf, nil + } +} +func binaryFromNative(cr *codecInfo) func(buf []byte, datum interface{}) ([]byte, error) { + return func(buf []byte, datum interface{}) ([]byte, error) { + var err error + switch v := datum.(type) { + case nil: + index, ok := cr.indexFromName["null"] + if !ok { + return nil, fmt.Errorf("cannot encode binary union: no member schema types support datum: allowed types: %v; received: %T", cr.allowedTypes, datum) + } + return longBinaryFromNative(buf, index) + case map[string]interface{}: + if len(v) != 1 { + err = fmt.Errorf("cannot encode binary union: non-nil Union values ought to be specified with Go map[string]interface{}, with single key equal to type name, and value equal to datum value: %v; received: %T", cr.allowedTypes, datum) + } else { + // will execute exactly once + for key, value := range v { + index, ok := cr.indexFromName[key] + if !ok { + err = fmt.Errorf("cannot encode binary union: no member schema types support datum: allowed types: %v; received: %T", cr.allowedTypes, datum) + } else { + return doBinaryFromNative(cr, index, buf, value) + } + } + } + case []interface{}: + if len(v) == 2 { + // EOS specific variant serialization + if eosTypeName, ok := v[0].(string); ok { + value := v[1] + if avroTypeName, ok := eosToAvro[eosTypeName]; ok { + return binaryFromNative(cr)(buf, Union(avroTypeName, value)) + } else { + err = fmt.Errorf("cannot encode binary union on eos input: the eos type is not supported %s; avro union definition: %v; received: %T", eosTypeName, cr.allowedTypes, datum) + } + } else { + err = fmt.Errorf("unable to cast eos type '%v' as string type", v[0]) + } + + } else { + err = fmt.Errorf("cannot encode binary union on eos input: the input must be an array of 2 entries where the first is a string that represents the eos type and the second the actual value, the value must be supported by the avro union definition: %v; received: %T", cr.allowedTypes, datum) + } + + } + if cr.nullable() { + index := cr.indexFromName["null"] + if index == 0 { + index = 1 + } else { + index = 0 + } + return doBinaryFromNative(cr, index, buf, datum) + } + if err != nil { + return nil, err + } + return nil, fmt.Errorf("cannot encode binary union: non-nil Union values ought to be specified with Go map[string]interface{}, with single key equal to type name, and value equal to datum value: %v; received: %T", cr.allowedTypes, datum) + } +} + +func doBinaryFromNative(cr *codecInfo, index int, buf []byte, value interface{}) ([]byte, error) { + c := cr.codecFromIndex[index] + buf, _ = longBinaryFromNative(buf, index) + return c.binaryFromNative(buf, value) +} + +func nativeFromTextual(cr *codecInfo) func(buf []byte) (interface{}, []byte, error) { + return func(buf []byte) (interface{}, []byte, error) { + if len(buf) >= 4 && bytes.Equal(buf[:4], []byte("null")) { + if _, ok := cr.indexFromName["null"]; ok { + return nil, buf[4:], nil + } + } + + var datum interface{} + var err error + datum, buf, err = genericMapTextDecoder(buf, nil, cr.codecFromName) + if err != nil { + return nil, nil, fmt.Errorf("cannot decode textual union: %s", err) + } + + return datum, buf, nil + } +} +func textualFromNative(cr *codecInfo) func(buf []byte, datum interface{}) ([]byte, error) { + return func(buf []byte, datum interface{}) ([]byte, error) { + switch v := datum.(type) { + case nil: + _, ok := cr.indexFromName["null"] + if !ok { + return nil, fmt.Errorf("cannot encode textual union: no member schema types support datum: allowed types: %v; received: %T", cr.allowedTypes, datum) + } + return append(buf, "null"...), nil + case map[string]interface{}: + if len(v) != 1 { + return nil, fmt.Errorf("cannot encode textual union: non-nil Union values ought to be specified with Go map[string]interface{}, with single key equal to type name, and value equal to datum value: %v; received: %T", cr.allowedTypes, datum) + } + // will execute exactly once + for key, value := range v { + index, ok := cr.indexFromName[key] + if !ok { + return nil, fmt.Errorf("cannot encode textual union: no member schema types support datum: allowed types: %v; received: %T", cr.allowedTypes, datum) + } + buf = append(buf, '{') + var err error + buf, err = stringTextualFromNative(buf, key) + if err != nil { + return nil, fmt.Errorf("cannot encode textual union: %s", err) + } + buf = append(buf, ':') + c := cr.codecFromIndex[index] + buf, err = c.textualFromNative(buf, value) + if err != nil { + return nil, fmt.Errorf("cannot encode textual union: %s", err) + } + return append(buf, '}'), nil + } + } + return nil, fmt.Errorf("cannot encode textual union: non-nil values ought to be specified with Go map[string]interface{}, with single key equal to type name, and value equal to datum value: %v; received: %T", cr.allowedTypes, datum) + } +} +func buildCodecForTypeDescribedBySlice(converters map[string]ConvertBuild, st map[string]*Codec, enclosingNamespace string, schemaArray []interface{}, cb *codecBuilder) (*Codec, error) { + if len(schemaArray) == 0 { + return nil, errors.New("Union ought to have one or more members") + } + + cr, err := makeCodecInfo(converters, st, enclosingNamespace, schemaArray, cb) + if err != nil { + return nil, err + } + + rv := &Codec{ + // NOTE: To support record field default values, union schema set to the + // type name of first member + // TODO: add/change to schemaCanonical below + schemaOriginal: cr.codecFromIndex[0].typeName.fullName, + + typeName: &name{"union", nullNamespace}, + nativeFromBinary: nativeFromBinary(&cr), + binaryFromNative: binaryFromNative(&cr), + nativeFromTextual: nativeFromTextual(&cr), + textualFromNative: textualFromNative(&cr), + } + return rv, nil +} + +// Standard JSON +// +// The default avro library supports a json that would result from your data into json +// instead of serializing it into binary +// +// JSON in the wild differs from that in one critical way - unions +// the avro spec requires unions to have their type indicated +// which means every value that is of a union type +// is actually sent as a small map {"string", "some string"} +// instead of simply as the value itself, which is the way of wild JSON +// https://avro.apache.org/docs/current/spec.html#json_encoding +// +// In order to use this to avro encode standard json the unions have to be rewritten +// so the can encode into unions as expected by the avro schema +// +// so the technique is to read in the json in the usual way +// when a union type is found, read the next json object +// try to figure out if it fits into any of the types +// that are specified for the union per the supplied schema +// if so, then wrap the value into a map and return the expected Union +// +// the json is morphed on the read side +// and then it will remain avro-json object +// avro data is not serialized back into standard json +// the data goes to avro-json and stays that way +func buildCodecForTypeDescribedBySliceJSON(converters map[string]ConvertBuild, st map[string]*Codec, enclosingNamespace string, schemaArray []interface{}, cb *codecBuilder) (*Codec, error) { + if len(schemaArray) == 0 { + return nil, errors.New("Union ought to have one or more members") + } + + cr, err := makeCodecInfo(converters, st, enclosingNamespace, schemaArray, cb) + if err != nil { + return nil, err + } + + rv := &Codec{ + // NOTE: To support record field default values, union schema set to the + // type name of first member + // TODO: add/change to schemaCanonical below + schemaOriginal: cr.codecFromIndex[0].typeName.fullName, + + typeName: &name{"union", nullNamespace}, + nativeFromBinary: nativeFromBinary(&cr), + binaryFromNative: binaryFromNative(&cr), + nativeFromTextual: nativeAvroFromTextualJson(&cr), + textualFromNative: textualFromNative(&cr), + } + return rv, nil +} + +func checkAll(allowedTypes []string, cr *codecInfo, buf []byte) (interface{}, []byte, error) { + for _, name := range cr.allowedTypes { + if name == "null" { + // skip null since we know we already got type float64 + continue + } + theCodec, ok := cr.codecFromName[name] + if !ok { + continue + } + rv, rb, err := theCodec.NativeFromTextual(buf) + if err != nil { + continue + } + return map[string]interface{}{name: rv}, rb, nil + } + return nil, buf, fmt.Errorf("could not decode any json data in input %v", string(buf)) +} +func nativeAvroFromTextualJson(cr *codecInfo) func(buf []byte) (interface{}, []byte, error) { + return func(buf []byte) (interface{}, []byte, error) { + + reader := bytes.NewReader(buf) + dec := json.NewDecoder(reader) + var m interface{} + + // i should be able to grab the next json "value" with decoder.Decode() + // https://pkg.go.dev/encoding/json#Decoder.Decode + // that dec.More() loop will give the next + // whatever then dec.Decode(&m) + // if m is interface{} + // it goes one legit json object at a time like this + // json.Delim: [ + // Q:map[string]interface {}: map[Name:Ed Text:Knock knock.] + // Q:map[string]interface {}: map[Name:Sam Text:Who's there?] + // Q:map[string]interface {}: map[Name:Ed Text:Go fmt.] + // Q:map[string]interface {}: map[Name:Sam Text:Go fmt who?] + // Q:map[string]interface {}: map[Name:Ed Text:Go fmt yourself!] + // string: eewew + // bottom:json.Delim: ] + // + // so right here, grab whatever this object is + // grab the object specified as the value + // and try to figure out what it is and handle it + err := dec.Decode(&m) + if err != nil { + return nil, buf, err + } + + allowedTypes := cr.allowedTypes + + switch m.(type) { + case nil: + if len(buf) >= 4 && bytes.Equal(buf[:4], []byte("null")) { + if _, ok := cr.codecFromName["null"]; ok { + return nil, buf[4:], nil + } + } + case float64: + // dec.Decode turns them all into float64 + // avro spec knows about int, long (variable length zig-zag) + // and then float and double (32 bits, 64 bits) + // https://avro.apache.org/docs/current/spec.html#binary_encode_primitive + // + + // double + // doubleNativeFromTextual + // float + // floatNativeFromTextual + // long + // longNativeFromTextual + // int + // intNativeFromTextual + + // sorted so it would be + // double, float, int, long + // that makes the priorities right by chance + sort.Strings(cr.allowedTypes) + + case map[string]interface{}: + + // try to decode it as a map + // because a map should fail faster than a record + // if that fails assume record and return it + sort.Strings(cr.allowedTypes) + } + + return checkAll(allowedTypes, cr, buf) + + } +} diff --git a/fork/goavro/union_test.go b/fork/goavro/union_test.go new file mode 100644 index 0000000..f65569a --- /dev/null +++ b/fork/goavro/union_test.go @@ -0,0 +1,436 @@ +// Copyright [2019] LinkedIn Corp. 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. + +package goavro + +import ( + "bytes" + "fmt" + "math" + "strconv" + "testing" +) + +func TestSchemaUnion(t *testing.T) { + testSchemaInvalid(t, `[{"type":"enum","name":"e1","symbols":["alpha","bravo"]},"e1"]`, "Union item 2 ought to be unique type") + testSchemaInvalid(t, `[{"type":"enum","name":"com.example.one","symbols":["red","green","blue"]},{"type":"enum","name":"one","namespace":"com.example","symbols":["dog","cat"]}]`, "Union item 2 ought to be unique type") +} + +func TestUnion(t *testing.T) { + testBinaryCodecPass(t, `["null"]`, Union("null", nil), []byte("\x00")) + testBinaryCodecPass(t, `["null","int"]`, Union("null", nil), []byte("\x00")) + testBinaryCodecPass(t, `["int","null"]`, Union("null", nil), []byte("\x02")) + + testBinaryCodecPass(t, `["null","int"]`, Union("int", 3), []byte("\x02\x06")) + testBinaryCodecPass(t, `["null","long"]`, Union("long", 3), []byte("\x02\x06")) + + testBinaryCodecPass(t, `["int","null"]`, Union("int", 3), []byte("\x00\x06")) + testBinaryEncodePass(t, `["int","null"]`, Union("int", 3), []byte("\x00\x06")) // can encode a bare 3 + + testBinaryEncodeFail(t, `[{"type":"enum","name":"colors","symbols":["red","green","blue"]},{"type":"enum","name":"animals","symbols":["dog","cat"]}]`, Union("colors", "bravo"), "value ought to be member of symbols") + testBinaryEncodeFail(t, `[{"type":"enum","name":"colors","symbols":["red","green","blue"]},{"type":"enum","name":"animals","symbols":["dog","cat"]}]`, Union("animals", "bravo"), "value ought to be member of symbols") + testBinaryCodecPass(t, `[{"type":"enum","name":"colors","symbols":["red","green","blue"]},{"type":"enum","name":"animals","symbols":["dog","cat"]}]`, Union("colors", "green"), []byte{0, 2}) + testBinaryCodecPass(t, `[{"type":"enum","name":"colors","symbols":["red","green","blue"]},{"type":"enum","name":"animals","symbols":["dog","cat"]}]`, Union("animals", "cat"), []byte{2, 2}) + + testUnableUnionPass(t, `["null","int"]`, 3, map[string]interface{}{"int": 3}) +} + +func TestUnionRejectInvalidType(t *testing.T) { + // testBinaryEncodeFailBadDatumType(t, `["null","long"]`, 3) + testBinaryEncodeFailBadDatumType(t, `["null","int","long","float"]`, float64(3.5)) + testBinaryEncodeFailBadDatumType(t, `["null","long"]`, Union("int", 3)) + testBinaryEncodeFailBadDatumType(t, `["null","int","long","float"]`, Union("double", float64(3.5))) +} + +func TestUnionWillCoerceTypeIfPossible(t *testing.T) { + testBinaryCodecPass(t, `["null","long","float","double"]`, Union("long", int32(3)), []byte("\x02\x06")) + testBinaryCodecPass(t, `["null","int","float","double"]`, Union("int", int64(3)), []byte("\x02\x06")) + testBinaryCodecPass(t, `["null","int","long","double"]`, Union("double", float32(3.5)), []byte("\x06\x00\x00\x00\x00\x00\x00\f@")) + testBinaryCodecPass(t, `["null","int","long","float"]`, Union("float", float64(3.5)), []byte("\x06\x00\x00\x60\x40")) +} + +func TestUnionNumericCoercionGuardsPrecision(t *testing.T) { + testBinaryEncodeFail(t, `["null","int","long","double"]`, Union("int", float32(3.5)), "lose precision") +} + +func TestUnionWithArray(t *testing.T) { + testBinaryCodecPass(t, `["null",{"type":"array","items":"int"}]`, Union("null", nil), []byte("\x00")) + + testBinaryCodecPass(t, `["null",{"type":"array","items":"int"}]`, Union("array", []interface{}{}), []byte("\x02\x00")) + testBinaryCodecPass(t, `["null",{"type":"array","items":"int"}]`, Union("array", []interface{}{1}), []byte("\x02\x02\x02\x00")) + testBinaryCodecPass(t, `["null",{"type":"array","items":"int"}]`, Union("array", []interface{}{1, 2}), []byte("\x02\x04\x02\x04\x00")) + + testBinaryCodecPass(t, `[{"type": "array", "items": "string"}, "null"]`, Union("null", nil), []byte{2}) + testBinaryCodecPass(t, `[{"type": "array", "items": "string"}, "null"]`, Union("array", []string{"foo"}), []byte("\x00\x02\x06foo\x00")) + testBinaryCodecPass(t, `[{"type": "array", "items": "string"}, "null"]`, Union("array", []string{"foo", "bar"}), []byte("\x00\x04\x06foo\x06bar\x00")) +} + +func TestUnionWithMap(t *testing.T) { + testBinaryCodecPass(t, `["null",{"type":"map","values":"string"}]`, Union("null", nil), []byte("\x00")) + testBinaryCodecPass(t, `["string",{"type":"map","values":"string"}]`, Union("map", map[string]interface{}{"He": "Helium"}), []byte("\x02\x02\x04He\x0cHelium\x00")) + testBinaryCodecPass(t, `["string",{"type":"array","items":"string"}]`, Union("string", "Helium"), []byte("\x00\x0cHelium")) +} + +func TestUnionDynamicEncode(t *testing.T) { + testBinaryEncodePass(t, `["null","long"]`, 3, []byte("\x02\x06")) + // TODO add more test cases +} + +func TestUnionEosEncode(t *testing.T) { + // output first parameter is type position, second is value + + // Boolean Type + testBinaryEncodePass(t, `["null","boolean"]`, []interface{}{"bool", true}, []byte("\x02\x01")) + testBinaryEncodePass(t, `["null","boolean"]`, []interface{}{"bool", 1}, []byte("\x02\x01")) + testBinaryEncodePass(t, `["null","boolean"]`, []interface{}{"bool", "true"}, []byte("\x02\x01")) + testBinaryEncodePass(t, `["null","boolean"]`, []interface{}{"bool", false}, []byte("\x02\x00")) + testBinaryEncodePass(t, `["null","boolean"]`, []interface{}{"bool", 0}, []byte("\x02\x00")) + + // Integer Types (int, uint mapped to int or long as per mapping) + testBinaryEncodePass(t, `["null","int"]`, []interface{}{"int8", int8(5)}, []byte("\x02\x0a")) + testBinaryEncodePass(t, `["null","int"]`, []interface{}{"int8", "5"}, []byte("\x02\x0a")) + testBinaryEncodePass(t, `["null","int"]`, []interface{}{"uint8", uint8(5)}, []byte("\x02\x0a")) + testBinaryEncodePass(t, `["null","int"]`, []interface{}{"uint8", "5"}, []byte("\x02\x0a")) + testBinaryEncodePass(t, `["null","int"]`, []interface{}{"int16", int16(300)}, []byte("\x02\xd8\x04")) + testBinaryEncodePass(t, `["null","int"]`, []interface{}{"int16", "300"}, []byte("\x02\xd8\x04")) + testBinaryEncodePass(t, `["null","int"]`, []interface{}{"uint16", uint16(300)}, []byte("\x02\xd8\x04")) + testBinaryEncodePass(t, `["null","int"]`, []interface{}{"uint16", "300"}, []byte("\x02\xd8\x04")) + testBinaryEncodePass(t, `["null","int"]`, []interface{}{"int32", int32(3)}, []byte("\x02\x06")) + testBinaryEncodePass(t, `["null","int"]`, []interface{}{"int32", "3"}, []byte("\x02\x06")) + testBinaryEncodePass(t, `["null","long"]`, []interface{}{"uint32", uint32(3)}, []byte("\x02\x06")) + testBinaryEncodePass(t, `["null","long"]`, []interface{}{"uint32", "3"}, []byte("\x02\x06")) + testBinaryEncodePass(t, `["null","long"]`, []interface{}{"int64", int64(10)}, []byte("\x02\x14")) + testBinaryEncodePass(t, `["null","long"]`, []interface{}{"int64", "10"}, []byte("\x02\x14")) + testBinaryEncodePass(t, `["null","long"]`, []interface{}{"uint64", uint64(10)}, []byte("\x02\x14")) + testBinaryEncodePass(t, `["null","long"]`, []interface{}{"uint64", "10"}, []byte("\x02\x14")) + testBinaryEncodePass(t, `["null","int"]`, []interface{}{"varint32", int32(20)}, []byte("\x02\x28")) + testBinaryEncodePass(t, `["null","int"]`, []interface{}{"varint32", "20"}, []byte("\x02\x28")) + testBinaryEncodePass(t, `["null","long"]`, []interface{}{"varuint32", uint32(20)}, []byte("\x02\x28")) + testBinaryEncodePass(t, `["null","long"]`, []interface{}{"varuint32", "20"}, []byte("\x02\x28")) + + // Floating Point Types + testBinaryEncodePass(t, `["null","float"]`, []interface{}{"float32", "0.12300000339746475"}, []byte("\x02\x6d\xe7\xfb\x3d")) + testBinaryEncodePass(t, `["null","float"]`, []interface{}{"float32", float32(0.123)}, []byte("\x02\x6d\xe7\xfb\x3d")) + testBinaryEncodePass(t, `["null","double"]`, []interface{}{"float64", "0.12300000339746475"}, []byte("\x02\x00\x00\x00\xa0\xed\x7c\xbf\x3f")) + testBinaryEncodePass(t, `["null","double"]`, []interface{}{"float64", float64(0.123)}, []byte("\x02\xb0\x72\x68\x91\xed\x7c\xbf\x3f")) + + // Time and Timestamp Types + testBinaryEncodePass(t, `["null","long"]`, []interface{}{"time_point", int64(1609459200)}, []byte("\x02\x80\x98\xf3\xfe\x0b")) + testBinaryEncodePass(t, `["null","long"]`, []interface{}{"time_point", "1609459200"}, []byte("\x02\x80\x98\xf3\xfe\x0b")) + testBinaryEncodePass(t, `["null","long"]`, []interface{}{"time_point_sec", int64(1609459200)}, []byte("\x02\x80\x98\xf3\xfe\x0b")) + testBinaryEncodePass(t, `["null","long"]`, []interface{}{"time_point_sec", "1609459200"}, []byte("\x02\x80\x98\xf3\xfe\x0b")) + testBinaryEncodePass(t, `["null","long"]`, []interface{}{"block_timestamp_type", int64(1609459200)}, []byte("\x02\x80\x98\xf3\xfe\x0b")) + testBinaryEncodePass(t, `["null","long"]`, []interface{}{"block_timestamp_type", "1609459200"}, []byte("\x02\x80\x98\xf3\xfe\x0b")) + + // String Types + testBinaryEncodePass(t, `["null","string"]`, []interface{}{"name", "ultra"}, []byte("\x02\x0a\x75\x6c\x74\x72\x61")) + testBinaryEncodePass(t, `["null","string"]`, []interface{}{"string", "hello"}, []byte("\x02\x0a\x68\x65\x6c\x6c\x6f")) + testBinaryEncodePass(t, `["null","string"]`, []interface{}{"symbol", "USD"}, []byte("\x02\x06\x55\x53\x44")) + testBinaryEncodePass(t, `["null","string"]`, []interface{}{"symbol_code", "EUR"}, []byte("\x02\x06\x45\x55\x52")) + + // Byte/Bytes Types + // If it happends; then GLHF + // testBinaryEncodePass(t, `["null","bytes"]`, []interface{}{"bytes", []byte{0x01, 0x02, 0x03}}, []byte("\x00\x06\x01\x02\x03")) + // testBinaryEncodePass(t, `["null","bytes"]`, []interface{}{"checksum160", []byte{0x01, 0x02, 0x03, 0x04, 0x05}}, []byte("\x00\x0a\x01\x02\x03\x04\x05")) + // testBinaryEncodePass(t, `["null","bytes"]`, []interface{}{"checksum256", []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}}, []byte("\x00\x10\x01\x02\x03\x04\x05\x06\x07\x08")) + // testBinaryEncodePass(t, `["null","bytes"]`, []interface{}{"checksum512", []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a}}, []byte("\x00\x14\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a")) + + testBinaryEncodePass(t, `["string","int","long"]`, []interface{}{"name", "ultra"}, []byte("\x00\x0a\x75\x6c\x74\x72\x61")) + +} + +func TestUnionMapRecordFitsInRecord(t *testing.T) { + // union value may be either map or a record + codec, err := NewCodec(`["null",{"type":"map","values":"double"},{"type":"record","name":"com.example.record","fields":[{"name":"field1","type":"int"},{"name":"field2","type":"float"}]}]`) + if err != nil { + t.Fatal(err) + } + + // the provided datum value could be encoded by either the map or the record + // schemas above + datum := map[string]interface{}{ + "field1": 3, + "field2": 3.5, + } + datumIn := Union("com.example.record", datum) + + buf, err := codec.BinaryFromNative(nil, datumIn) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(buf, []byte{ + 0x04, // prefer record (union item 2) over map (union item 1) + 0x06, // field1 == 3 + 0x00, 0x00, 0x60, 0x40, // field2 == 3.5 + }) { + t.Errorf("GOT: %#v; WANT: %#v", buf, []byte{byte(2)}) + } + + // round trip + datumOut, buf, err := codec.NativeFromBinary(buf) + if err != nil { + t.Fatal(err) + } + if actual, expected := len(buf), 0; actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } + + datumOutMap, ok := datumOut.(map[string]interface{}) + if !ok { + t.Fatalf("GOT: %#v; WANT: %#v", ok, false) + } + if actual, expected := len(datumOutMap), 1; actual != expected { + t.Fatalf("GOT: %#v; WANT: %#v", actual, expected) + } + datumValue, ok := datumOutMap["com.example.record"] + if !ok { + t.Fatalf("GOT: %#v; WANT: %#v", datumOutMap, "have `com.example.record` key") + } + datumValueMap, ok := datumValue.(map[string]interface{}) + if !ok { + t.Errorf("GOT: %#v; WANT: %#v", ok, true) + } + if actual, expected := len(datumValueMap), len(datum); actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } + for k, v := range datum { + if actual, expected := fmt.Sprintf("%v", datumValueMap[k]), fmt.Sprintf("%v", v); actual != expected { + t.Errorf("GOT: %#v; WANT: %#v", actual, expected) + } + } +} + +func TestUnionRecordFieldWhenNull(t *testing.T) { + schema := `{ + "type": "record", + "name": "r1", + "fields": [ + {"name": "f1", "type": [{"type": "array", "items": "string"}, "null"]} + ] +}` + + testBinaryCodecPass(t, schema, map[string]interface{}{"f1": Union("array", []interface{}{})}, []byte("\x00\x00")) + testBinaryCodecPass(t, schema, map[string]interface{}{"f1": Union("array", []string{"bar"})}, []byte("\x00\x02\x06bar\x00")) + testBinaryCodecPass(t, schema, map[string]interface{}{"f1": Union("array", []string{})}, []byte("\x00\x00")) + testBinaryCodecPass(t, schema, map[string]interface{}{"f1": Union("null", nil)}, []byte("\x02")) + testBinaryCodecPass(t, schema, map[string]interface{}{"f1": nil}, []byte("\x02")) +} + +func TestUnionText(t *testing.T) { + testTextEncodeFail(t, `["null","int"]`, Union("null", 3), "expected") + testTextCodecPass(t, `["null","int"]`, Union("null", nil), []byte("null")) + testTextCodecPass(t, `["null","int"]`, Union("int", 3), []byte(`{"int":3}`)) + testTextCodecPass(t, `["null","int","string"]`, Union("string", "😂 "), []byte(`{"string":"\u0001\uD83D\uDE02 "}`)) +} + +func ExampleUnion() { + codec, err := NewCodec(`["null","string","int"]`) + if err != nil { + fmt.Println(err) + } + buf, err := codec.TextualFromNative(nil, Union("string", "some string")) + if err != nil { + fmt.Println(err) + } + fmt.Println(string(buf)) + // Output: {"string":"some string"} +} + +func ExampleUnion3() { + // Imagine a record field with the following union type. I have seen this + // sort of type in many schemas. I have been told the reasoning behind it is + // when the writer desires to encode data to JSON that cannot be written as + // a JSON number, then to encode it as a string and allow the reader to + // parse the string accordingly. + codec, err := NewCodec(`["null","double","string"]`) + if err != nil { + fmt.Println(err) + } + + native, _, err := codec.NativeFromTextual([]byte(`{"string":"NaN"}`)) + if err != nil { + fmt.Println(err) + } + + value := math.NaN() + if native == nil { + fmt.Print("decoded null: ") + } else { + for k, v := range native.(map[string]interface{}) { + switch k { + case "double": + fmt.Print("decoded double: ") + value = v.(float64) + case "string": + fmt.Print("decoded string: ") + s := v.(string) + switch s { + case "NaN": + value = math.NaN() + case "+Infinity": + value = math.Inf(1) + case "-Infinity": + value = math.Inf(-1) + default: + var err error + value, err = strconv.ParseFloat(s, 64) + if err != nil { + fmt.Println(err) + } + } + } + } + } + fmt.Println(value) + // Output: decoded string: NaN +} + +func ExampleJSONUnion() { + codec, err := NewCodec(`["null","string","int"]`) + if err != nil { + fmt.Println(err) + } + buf, err := codec.TextualFromNative(nil, Union("string", "some string")) + if err != nil { + fmt.Println(err) + } + fmt.Println(string(buf)) + // Output: {"string":"some string"} +} + +// +// The following examples show the way to put a new codec into use +// Currently the only new codec is ont that supports standard json +// which does not indicate unions in any way +// so standard json data needs to be guided into avro unions + +// show how to use the default codec via the NewCodecFrom mechanism +func ExampleCustomCodec() { + codec, err := NewCodecFrom(`"string"`, &codecBuilder{ + buildCodecForTypeDescribedByMap, + buildCodecForTypeDescribedByString, + buildCodecForTypeDescribedBySlice, + }, nil) + if err != nil { + fmt.Println(err) + } + buf, err := codec.TextualFromNative(nil, "some string 22") + if err != nil { + fmt.Println(err) + } + fmt.Println(string(buf)) + // Output: "some string 22" +} + +// Use the standard JSON codec instead +func ExampleJSONStringToTextual() { + codec, err := NewCodecFrom(`["null","string","int"]`, &codecBuilder{ + buildCodecForTypeDescribedByMap, + buildCodecForTypeDescribedByString, + buildCodecForTypeDescribedBySliceJSON, + }, nil) + if err != nil { + fmt.Println(err) + } + buf, err := codec.TextualFromNative(nil, Union("string", "some string")) + if err != nil { + fmt.Println(err) + } + fmt.Println(string(buf)) + // Output: {"string":"some string"} +} + +func ExampleJSONStringToNative() { + codec, err := NewCodecFrom(`["null","string","int"]`, &codecBuilder{ + buildCodecForTypeDescribedByMap, + buildCodecForTypeDescribedByString, + buildCodecForTypeDescribedBySliceJSON, + }, nil) + if err != nil { + fmt.Println(err) + } + // send in a legit json string + t, _, err := codec.NativeFromTextual([]byte("\"some string one\"")) + if err != nil { + fmt.Println(err) + } + // see it parse into a map like the avro encoder does + o, ok := t.(map[string]interface{}) + if !ok { + fmt.Printf("its a %T not a map[string]interface{}", t) + } + + // pull out the string to show its all good + _v := o["string"] + v, ok := _v.(string) + fmt.Println(v) + // Output: some string one +} + +func TestUnionJSON(t *testing.T) { + testJSONDecodePass(t, `["null","int"]`, nil, []byte("null")) + testJSONDecodePass(t, `["null","int","long"]`, Union("int", 3), []byte(`3`)) + testJSONDecodePass(t, `["null","long","int"]`, Union("int", 3), []byte(`3`)) + testJSONDecodePass(t, `["null","int","long"]`, Union("long", 333333333333333), []byte(`333333333333333`)) + testJSONDecodePass(t, `["null","long","int"]`, Union("long", 333333333333333), []byte(`333333333333333`)) + testJSONDecodePass(t, `["null","float","int","long"]`, Union("float", 6.77), []byte(`6.77`)) + testJSONDecodePass(t, `["null","int","float","long"]`, Union("float", 6.77), []byte(`6.77`)) + testJSONDecodePass(t, `["null","double","int","long"]`, Union("double", 6.77), []byte(`6.77`)) + testJSONDecodePass(t, `["null","int","float","double","long"]`, Union("double", 6.77), []byte(`6.77`)) + testJSONDecodePass(t, `["null",{"type":"array","items":"int"}]`, Union("array", []interface{}{1, 2}), []byte(`[1,2]`)) + testJSONDecodePass(t, `["null",{"type":"map","values":"int"}]`, Union("map", map[string]interface{}{"k1": 13}), []byte(`{"k1":13}`)) + testJSONDecodePass(t, `["null",{"name":"r1","type":"record","fields":[{"name":"field1","type":"string"},{"name":"field2","type":"string"}]}]`, Union("r1", map[string]interface{}{"field1": "value1", "field2": "value2"}), []byte(`{"field1": "value1", "field2": "value2"}`)) + testJSONDecodePass(t, `["null","boolean"]`, Union("boolean", true), []byte(`true`)) + testJSONDecodePass(t, `["null","boolean"]`, Union("boolean", false), []byte(`false`)) + testJSONDecodePass(t, `["null",{"type":"enum","name":"e1","symbols":["alpha","bravo"]}]`, Union("e1", "bravo"), []byte(`"bravo"`)) + testJSONDecodePass(t, `["null", "bytes"]`, Union("bytes", []byte("")), []byte("\"\"")) + testJSONDecodePass(t, `["null", "bytes", "string"]`, Union("bytes", []byte("")), []byte("\"\"")) + testJSONDecodePass(t, `["null", "string", "bytes"]`, Union("string", "value1"), []byte(`"value1"`)) + testJSONDecodePass(t, `["null", {"type":"enum","name":"e1","symbols":["alpha","bravo"]}, "string"]`, Union("e1", "bravo"), []byte(`"bravo"`)) + testJSONDecodePass(t, `["null", {"type":"fixed","name":"f1","size":4}]`, Union("f1", []byte(`abcd`)), []byte(`"abcd"`)) + testJSONDecodePass(t, `"string"`, "abcd", []byte(`"abcd"`)) + testJSONDecodePass(t, `{"type":"record","name":"kubeEvents","fields":[{"name":"field1","type":"string","default":""}]}`, map[string]interface{}{"field1": "value1"}, []byte(`{"field1":"value1"}`)) + testJSONDecodePass(t, `{"type":"record","name":"kubeEvents","fields":[{"name":"field1","type":"string","default":""},{"name":"field2","type":"string"}]}`, map[string]interface{}{"field1": "", "field2": "deef"}, []byte(`{"field2": "deef"}`)) + testJSONDecodePass(t, `{"type":"record","name":"kubeEvents","fields":[{"name":"field1","type":["string","null"],"default":""}]}`, map[string]interface{}{"field1": Union("string", "value1")}, []byte(`{"field1":"value1"}`)) + testJSONDecodePass(t, `{"type":"record","name":"kubeEvents","fields":[{"name":"field1","type":["string","null"],"default":""}]}`, map[string]interface{}{"field1": nil}, []byte(`{"field1":null}`)) + // union of null which has minimal syntax + testJSONDecodePass(t, `{"type":"record","name":"LongList","fields":[{"name":"next","type":["null","LongList"],"default":null}]}`, map[string]interface{}{"next": nil}, []byte(`{"next": null}`)) + // record containing union of record (recursive record) + testJSONDecodePass(t, `{"type":"record","name":"LongList","fields":[{"name":"next","type":["null","LongList"],"default":null}]}`, map[string]interface{}{"next": Union("LongList", map[string]interface{}{"next": nil})}, []byte(`{"next":{"next":null}}`)) + testJSONDecodePass(t, `{"type":"record","name":"LongList","fields":[{"name":"next","type":["null","LongList"],"default":null}]}`, map[string]interface{}{"next": Union("LongList", map[string]interface{}{"next": Union("LongList", map[string]interface{}{"next": nil})})}, []byte(`{"next":{"next":{"next":null}}}`)) +} + +func testUnableUnionPass(t *testing.T, schema string, datum interface{}, expectedDatum interface{}) { + t.Helper() + codec, err := NewCodec(schema) + if err != nil { + t.Fatalf("Schema: %q %s", schema, err) + } + + actualBinary, err := codec.BinaryFromNative(nil, datum) + if err != nil { + t.Fatalf("schema: %s; Datum: %v; %s", schema, datum, err) + } + + value, remaining, err := codec.NativeFromBinary(actualBinary) + if err != nil { + t.Fatalf("schema: %s; %s", schema, err) + } + + // remaining ought to be empty because there is nothing remaining to be + // decoded + if actual, expected := len(remaining), 0; actual != expected { + t.Errorf("schema: %s; Datum: %v; Actual: %#v; Expected: %#v", schema, datum, actual, expected) + } + // for testing purposes, to prevent big switch statement, convert each to + // string and compare. + if actual, expected := fmt.Sprintf("%v", value), fmt.Sprintf("%v", expectedDatum); actual != expected { + t.Errorf("schema: %s; Datum: %v; Actual: %#v; Expected: %#v", schema, datum, actual, expected) + } +} diff --git a/gen.go b/gen.go new file mode 100644 index 0000000..9c2aa2d --- /dev/null +++ b/gen.go @@ -0,0 +1,424 @@ +package dkafka + +import ( + "encoding/json" + "fmt" + + "github.com/confluentinc/confluent-kafka-go/kafka" + pbcodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/codec/v1" + "github.com/google/cel-go/cel" + pbbstream "github.com/streamingfast/pbgo/dfuse/bstream/v1" + "go.uber.org/zap" +) + +type EntityType = string + +const ( + Action EntityType = "action" + Table EntityType = "table" +) + +type TransactionContext struct { + block *pbcodec.Block + stepName string + transaction *pbcodec.TransactionTrace + blockStep BlockStep + cursor string + step pbbstream.ForkStep +} + +type ActionContext struct { + TransactionContext + correlation *Correlation + actionTrace *pbcodec.ActionTrace +} + +type GeneratorAtActionLevel interface { + Apply(genContext ActionContext) ([]Generation2, error) +} + +type GeneratorAtTransactionLevel interface { + Apply(genContext TransactionContext) ([]*kafka.Message, error) +} + +type Generation2 struct { // TODO rename to Message + CeType string `json:"ce_type,omitempty"` + CeId []byte `json:"ce_id,omitempty"` + Key string `json:"key,omitempty"` + Value []byte `json:"value,omitempty"` + Headers []kafka.Header `json:"headers,omitempty"` +} + +type generation struct { + EntityType EntityType `json:"entityType,omitempty"` + EntityName string `json:"entityName,omitempty"` + Account string `json:"account,omitempty"` + CeType string `json:"ce_type,omitempty"` + CeId []byte `json:"ce_id,omitempty"` + Key string `json:"key,omitempty"` + Value interface{} `json:"value,omitempty"` +} + +type DecodeDBOp func(in *pbcodec.DBOp, blockNum uint32) (decodedDBOps *decodedDBOp, err error) + +type ExtractKey func(*pbcodec.DBOp) string + +func (g *generation) asCodecId() CodecId { + return CodecId{ + Account: g.Account, + Name: g.EntityName, + } +} + +// normalizeName return a 13 dotted string when the name value is empty otherwise it returns the given name +func normalizeName(name string) string { + if name == "" { + return "............." + } else { + return name + } +} + +func extractFullKey(dbOp *pbcodec.DBOp) string { + return fmt.Sprintf("%s:%s", extractScope(dbOp), extractPrimaryKey(dbOp)) +} + +func extractScope(dbOp *pbcodec.DBOp) string { + return normalizeName(dbOp.Scope) +} + +func extractPrimaryKey(dbOp *pbcodec.DBOp) string { + return normalizeName(dbOp.PrimaryKey) +} + +func indexDbOps(gc ActionContext) []*IndexedEntry[*pbcodec.DBOp] { + return orderSliceOnBlockStep(NewIndexedEntrySlice(gc.transaction.DBOpsForAction(gc.actionTrace.ExecutionIndex)), gc.step) +} + +type TableKeyExtractorFinder func(string) (ExtractKey, bool) + +type ActionKeyExtractorFinder func(string) (cel.Program, bool) + +type TableGenerator struct { + getExtractKey TableKeyExtractorFinder + abiCodec ABICodec +} + +type void struct{} +type StringSet = map[string]void + +func (tg TableGenerator) Apply(gc ActionContext) (generations []Generation2, err error) { + gens, err := tg.doApply(gc) + if err != nil { + return + } + for _, g := range gens { + codec, err := tg.abiCodec.GetCodec(g.asCodecId(), gc.block.Number) + if err != nil { + return nil, err + } + zlog.Debug("marshal table", zap.String("name", g.EntityName)) + value, err := codec.Marshal(nil, g.Value) + if err != nil { + zlog.Debug("fail fast on codec.Marshal()", zap.Error(err)) + return nil, err + } + generations = append(generations, Generation2{ + CeType: g.CeType, + CeId: g.CeId, + Key: g.Key, + Value: value, + Headers: codec.GetHeaders(), + }) + } + zlog.Debug("return messages after marshal operation", zap.Any("nb_messages", len(generations))) + return generations, nil +} + +func (tg TableGenerator) doApply(gc ActionContext) ([]generation, error) { + indexedDbOps := indexDbOps(gc) + generations := []generation{} + for _, indexedDbOp := range indexedDbOps { + dbOp := indexedDbOp.Entry + dbOpIndex := indexedDbOp.Index + if dbOp.Operation == pbcodec.DBOp_OPERATION_UNKNOWN { + continue + } + // extractor, found := tg.tableNames[dbOp.TableName] + extractKey, found := tg.getExtractKey(dbOp.TableName) + if !found { + continue + } + decodedDBOp, err := tg.abiCodec.DecodeDBOp(dbOp, gc.block.Number) + if err != nil { + return nil, err + } + key := extractKey(dbOp) + tableCamelCase, ceType := tableCeType(dbOp.TableName) + ceId := hashString(fmt.Sprintf( + "%s%s%d%d%s", + gc.cursor, + gc.transaction.Id, + gc.actionTrace.ExecutionIndex, + dbOpIndex, + gc.stepName, + )) + value := newTableNotification( + notificationContextMap(gc), + actionInfoBasicMap(gc), + decodedDBOp.asMap(dbOpRecordName(tableCamelCase), dbOpIndex), + ) + generation := generation{ + CeId: ceId, + CeType: ceType, + Key: key, + Value: value, + EntityType: Table, + EntityName: dbOp.TableName, + Account: dbOp.Code, + } + zlog.Debug("generated table message", zap.Any("generation", generation)) + generations = append(generations, generation) + } + zlog.Debug("return generated table messages", zap.Int("nb_generations", len(generations))) + return generations, nil +} + +type ActionGenerator2 struct { + keyExtractors ActionKeyExtractorFinder + abiCodec ABICodec + skipDbOps bool +} + +func (ag ActionGenerator2) Apply(gc ActionContext) ([]Generation2, error) { + gens, err := ag.doApply(gc) + if err != nil { + return nil, err + } + if len(gens) > 0 { + g := gens[0] + codec, err := ag.abiCodec.GetCodec(g.asCodecId(), gc.block.Number) + if err != nil { + return nil, err + } + value, err := codec.Marshal(nil, g.Value) + if err != nil { + return nil, err + } + return []Generation2{{ + CeType: g.CeType, + CeId: g.CeId, + Key: g.Key, + Value: value, + Headers: codec.GetHeaders(), + }}, nil + } else { + return nil, nil + } +} + +func (ag ActionGenerator2) doApply(gc ActionContext) ([]generation, error) { + actionName := gc.actionTrace.Action.Name + extractor, found := ag.keyExtractors(actionName) + if !found { + return nil, nil + } + activation, err := NewActionActivation( + gc.stepName, + gc.transaction, + gc.actionTrace, + ) + if err != nil { + return nil, err + } + key, err := evalString(extractor, activation) + if err != nil { + return nil, fmt.Errorf("fail to extract action key: %w", err) + } + _, ceType := actionCeType(actionName) + ceId := hashString(fmt.Sprintf( + "%s%s%d%s", + gc.cursor, + gc.transaction.Id, + gc.actionTrace.ExecutionIndex, + gc.stepName, + )) + jsonData := make(map[string]interface{}) + if stringData := gc.actionTrace.Action.JsonData; stringData != "" { + err = json.Unmarshal(json.RawMessage(stringData), &jsonData) + if err != nil { + return nil, err + } + } + var dbOpsGen []map[string]interface{} + if ag.skipDbOps { + // If we skip DB operations, we return an empty slice + dbOpsGen = []map[string]interface{}{} + } else { + // If we don't skip DB operations, we generate the DB operations + // from the indexed DB operations + indexedDbOps := indexDbOps(gc) + dbOpsGen = make([]map[string]interface{}, len(indexedDbOps)) + for _, dbOp := range indexedDbOps { + dbOpsGen[dbOp.Index] = newDBOpBasic(dbOp.Entry, dbOp.Index) + } + } + value := newActionNotification( + notificationContextMap(gc), + newActionInfo(actionInfoBasicMap(gc), jsonData, dbOpsGen), + ) + return []generation{{ + CeId: ceId, + CeType: ceType, + Key: key, + Value: value, + EntityType: Action, + EntityName: actionName, + Account: gc.actionTrace.Account(), + }}, nil +} + +func notificationContextMap(gc ActionContext) map[string]interface{} { + status := sanitizeStatus(gc.transaction.Receipt.Status.String()) + + return newNotificationContext( + gc.block.Id, + gc.block.Number, + status, + !gc.transaction.HasBeenReverted(), + gc.stepName, + gc.transaction.Id, + newOptionalCorrelation(gc.correlation), + gc.block.MustTime().UTC(), + gc.cursor, + ) +} + +func actionInfoBasicMap(gc ActionContext) map[string]interface{} { + var globalSeq uint64 + if receipt := gc.actionTrace.Receipt; receipt != nil { + globalSeq = receipt.GlobalSequence + } + + var authorizations []string + for _, auth := range gc.actionTrace.Action.Authorization { + authorizations = append(authorizations, auth.Authorization()) + } + + return newActionInfoBasic( + gc.actionTrace.Account(), + gc.actionTrace.Receiver, + gc.actionTrace.Name(), + globalSeq, + authorizations, + gc.actionTrace.ActionOrdinal, + gc.actionTrace.CreatorActionOrdinal, + gc.actionTrace.ClosestUnnotifiedAncestorActionOrdinal, + gc.actionTrace.ExecutionIndex, + ) +} + +type transaction2ActionsGenerator struct { + actionLevelGenerator GeneratorAtActionLevel + abiCodec ABICodec + headers []kafka.Header + topic string + account string +} + +func (t transaction2ActionsGenerator) isThisSmartContractABIUpdated(action *pbcodec.Action) bool { + return action.Name == "setabi" && (t.isSelfAuthorized(action.Authorization) || t.isSetABIOnTrackedAccount(action)) +} + +func (t transaction2ActionsGenerator) isSetABIOnTrackedAccount(action *pbcodec.Action) bool { + var actionParameters map[string]interface{} + if err := json.Unmarshal(json.RawMessage(action.JsonData), &actionParameters); err != nil { + return false + } + if account, found := actionParameters["account"]; found { + return account == t.account + } + return false +} + +func (t transaction2ActionsGenerator) isSelfAuthorized(authorizations []*pbcodec.PermissionLevel) bool { + for _, permissionLevel := range authorizations { + if permissionLevel.Actor == t.account { + return true + } + } + return false +} + +func (t transaction2ActionsGenerator) Apply(genContext TransactionContext) ([]*kafka.Message, error) { + msgs := make([]*kafka.Message, 0) + // manage correlation + trx := genContext.transaction + // blkStep := genContext.step + correlation, err := getCorrelation(trx.ActionTraces) + if err != nil { + return nil, err + } + acts := trx.ActionTraces + zlog.Debug("adapt transaction", zap.Uint32("block_num", genContext.block.Number), zap.Int("trx_index", int(trx.Index)), zap.Int("nb_acts", len(acts))) + for _, act := range orderSliceOnBlockStep(acts, genContext.step) { + if !act.FilteringMatched { + continue + } + if t.isThisSmartContractABIUpdated(act.Action) { + zlog.Info("new abi published defer clear ABI cache at end of this block parsing", zap.Uint32("block_num", genContext.block.Number), zap.Int("trx_index", int(trx.Index)), zap.String("trx_id", trx.Id)) + t.abiCodec.UpdateABI(genContext.block.Number, genContext.step, trx.Id, act) + continue + } + actionTracesReceived.Inc() + // generation + generations, err := t.actionLevelGenerator.Apply(ActionContext{ + TransactionContext: genContext, + actionTrace: act, + correlation: correlation, + }) + + if err != nil { + zlog.Debug("fail fast on generator.Apply()", zap.Error(err)) + return nil, err + } + + for _, generation := range generations { + headers := append(t.headers, + kafka.Header{ + Key: "ce_id", + Value: generation.CeId, + }, + kafka.Header{ + Key: "ce_type", + Value: []byte(generation.CeType), + }, + BlockStep{blk: genContext.block}.timeHeader(), + kafka.Header{ + Key: "ce_blkstep", + Value: []byte(genContext.stepName), + }, + ) + headers = append(headers, generation.Headers...) + if correlation != nil { + headers = append(headers, + kafka.Header{ + Key: "ce_parentid", + Value: []byte(correlation.Id), + }, + ) + } + msg := &kafka.Message{ + Key: []byte(generation.Key), + Headers: headers, + Value: generation.Value, + TopicPartition: kafka.TopicPartition{ + Topic: &t.topic, + Partition: kafka.PartitionAny, + }, + } + msgs = append(msgs, msg) + } + } + return msgs, nil +} diff --git a/gen_test.go b/gen_test.go new file mode 100644 index 0000000..bbff44d --- /dev/null +++ b/gen_test.go @@ -0,0 +1,167 @@ +package dkafka + +import ( + "fmt" + "testing" + + pbcodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/codec/v1" +) + +const emptyName = "............." + +func Test_normalizeName(t *testing.T) { + type args struct { + name string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "non-empty", + args: args{ + name: "eosio", + }, + want: "eosio", + }, + { + name: "empty", + args: args{ + name: "", + }, + want: emptyName, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := normalizeName(tt.args.name); got != tt.want { + t.Errorf("normalizeName() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_extractFullKey(t *testing.T) { + type args struct { + dbOp *pbcodec.DBOp + } + tests := []struct { + name string + args args + want string + }{ + { + name: "non-empty", + args: args{ + dbOp: &pbcodec.DBOp{ + Scope: "eosio", + PrimaryKey: "global", + }, + }, + want: "eosio:global", + }, + { + name: "empty-scope", + args: args{ + dbOp: &pbcodec.DBOp{ + PrimaryKey: "global", + }, + }, + want: fmt.Sprintf("%s:global", emptyName), + }, + { + name: "empty-primary-key", + args: args{ + dbOp: &pbcodec.DBOp{ + Scope: "eosio", + }, + }, + want: fmt.Sprintf("eosio:%s", emptyName), + }, + { + name: "empty-scope-and-primary-key", + args: args{ + dbOp: &pbcodec.DBOp{}, + }, + want: fmt.Sprintf("%s:%s", emptyName, emptyName), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := extractFullKey(tt.args.dbOp); got != tt.want { + t.Errorf("extractFullKey() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_extractScope(t *testing.T) { + type args struct { + dbOp *pbcodec.DBOp + } + tests := []struct { + name string + args args + want string + }{ + { + name: "with-scope", + args: args{ + dbOp: &pbcodec.DBOp{ + Scope: "eosio", + }, + }, + want: "eosio", + }, + { + name: "without-scope", + args: args{ + dbOp: &pbcodec.DBOp{}, + }, + want: emptyName, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := extractScope(tt.args.dbOp); got != tt.want { + t.Errorf("extractScope() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_extractPrimaryKey(t *testing.T) { + type args struct { + dbOp *pbcodec.DBOp + } + tests := []struct { + name string + args args + want string + }{ + { + name: "with-primary-key", + args: args{ + dbOp: &pbcodec.DBOp{ + PrimaryKey: "global", + }, + }, + want: "global", + }, + { + name: "without-primary-key", + args: args{ + dbOp: &pbcodec.DBOp{}, + }, + want: emptyName, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := extractPrimaryKey(tt.args.dbOp); got != tt.want { + t.Errorf("extractPrimaryKey() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/generator.go b/generator.go new file mode 100644 index 0000000..66dd316 --- /dev/null +++ b/generator.go @@ -0,0 +1,329 @@ +package dkafka + +import ( + "fmt" + "strconv" + "strings" + + pbcodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/codec/v1" + "github.com/google/cel-go/cel" +) + +type Generation struct { + Key string `json:"key,omitempty"` + CeType string `json:"type,omitempty"` + DecodedDBOps []*decodedDBOp `json:"db_ops,omitempty"` +} + +type Generator interface { + Apply( + stepName string, + transaction *pbcodec.TransactionTrace, + trace *pbcodec.ActionTrace, + decodedDBOps []*decodedDBOp, + ) ([]Generation, error) +} + +type ActionConf struct { + Filter string `json:"filter,omitempty"` + First string `json:"first,omitempty"` + Split bool `json:"split,omitempty"` + Key string `json:"key"` + CeType string `json:"type"` +} + +type ActionsConf = map[string][]ActionConf + +func NewActionsGenerator(config ActionsConf, skipKey ...bool) (Generator, error) { + handlersByAction := make(map[string][]actionHandler) + for actionName, actions := range config { + actionHandlers := make([]actionHandler, 0, 1) + for _, action := range actions { + if action.Filter != "" && action.First != "" { + return nil, fmt.Errorf("only one projection operator at a time per action handler is supported. Action:%s", actionName) + } + var proj projection + if action.Filter != "" { + matcher, err := expressionToMatcher(action.Filter) + if err != nil { + return nil, fmt.Errorf("invalid filter expression: %v, error: %w", action.Filter, err) + } + proj = filter{ + matcher: matcher, + } + } else if action.First != "" { + matcher, err := expressionToMatcher(action.First) + if err != nil { + return nil, fmt.Errorf("invalid first expression: %v, error: %w", action.First, err) + } + proj = first{ + table: matcher, + } + } else { + // indentity dbops projection + proj = indentity{} + } + skipK := (len(skipKey) > 0 && skipKey[0]) + var keyExtractor cel.Program + if action.Key != "" && !skipK { + var err error + keyExtractor, err = exprToCelProgram(action.Key) + if err != nil { + return nil, fmt.Errorf("action: %s, cannot parse key expression: %w", actionName, err) + } + } else if !skipK && action.Key == "" { + return nil, fmt.Errorf("missing key expression for action: %s", actionName) + } + if action.CeType == "" { + return nil, fmt.Errorf("missing type string for action: %s", actionName) + } + actionHandlers = append(actionHandlers, actionHandler{ + projection: proj, + ceType: action.CeType, + keyExtractor: keyExtractor, + split: action.Split, + }) + } + + handlersByAction[actionName] = actionHandlers + } + return ActionGenerator{ + actions: handlersByAction, + }, nil +} + +var operationTypeByName map[string]pbcodec.DBOp_Operation = map[string]pbcodec.DBOp_Operation{ + "UNKNOWN": 0, + "INSERT": 1, + "UPDATE": 2, + "DELETE": 3, +} + +func stringAsDBOpOperation(s string) (op pbcodec.DBOp_Operation, err error) { + if len(s) > 1 { // string representation of the operation + var found bool + op, found = operationTypeByName[strings.ToUpper(s)] + if !found { + err = fmt.Errorf("invalid operation matcher value must be one of: {UNKNOWN|INSERT|UPDATE|DELETE}, on: %s", s) + } + } else { // numerical representation of the operation [0..3] + // ParseUint + var op64 uint64 + op64, err = strconv.ParseUint(s, 10, 32) + if err != nil || op64 > 3 { + err = fmt.Errorf("invalid operation matcher value must be a uint between [0..3], on: %s, with error: %w", s, err) + } else { + op = pbcodec.DBOp_Operation(op64) + } + } + return +} + +func expressionToMatcher(expression string) (matcher, error) { + tokens := strings.Split(expression, ":") + if len(tokens) == 1 { // <- table name only short expression (legacy) => table-name + return tableNameMatcher{tokens[0]}, nil + } else if tokens[0] == "*" && tokens[1] == "*" { // <- invalid expression => *:* + return nil, fmt.Errorf("unsupported table matcher expression: %s, you must at least fix on of them", expression) + } else if tokens[0] == "*" { // <- table name only long expression => *:table-name + return tableNameMatcher{tokens[1]}, nil + } else if tokens[1] == "*" { // <- operation only expression => 1:*, INSERT:* + op, err := stringAsDBOpOperation(tokens[0]) + if err != nil { + return nil, err + } + return operationMatcher{op}, nil + } else { + op, err := stringAsDBOpOperation(tokens[0]) + if err != nil { + return nil, err + } + return operationOnTableMatcher{op: pbcodec.DBOp_Operation(op), name: tokens[1]}, nil + } +} + +func NewExpressionsGenerator( + eventKeyProg cel.Program, + eventTypeProg cel.Program, +) Generator { + return ExpressionsGenerator{ + keyExtractor: eventKeyProg, + ceTypeExtractor: eventTypeProg, + } +} + +type projection interface { + on(decodedDBOps []*decodedDBOp) []*decodedDBOp +} + +type first struct { + table matcher +} + +func (f first) on(decodedDBOps []*decodedDBOp) []*decodedDBOp { + for _, dbOp := range decodedDBOps { + if f.table.match(dbOp.DBOp) { + return []*decodedDBOp{dbOp} + } + } + return nil +} + +type filter struct { + matcher matcher +} + +func (f filter) on(decodedDBOps []*decodedDBOp) []*decodedDBOp { + var filteredDBOps []*decodedDBOp = make([]*decodedDBOp, 0, 1) + for _, dbOp := range decodedDBOps { + if f.matcher.match(dbOp.DBOp) { + filteredDBOps = append(filteredDBOps, dbOp) + } + } + return filteredDBOps +} + +type matcher interface { + match(value *pbcodec.DBOp) bool +} + +type tableNameMatcher struct { + name string +} + +func (m tableNameMatcher) match(value *pbcodec.DBOp) bool { + return m.name == value.TableName +} + +type operationMatcher struct { + op pbcodec.DBOp_Operation +} + +func (m operationMatcher) match(value *pbcodec.DBOp) bool { + return m.op == value.Operation +} + +type operationOnTableMatcher struct { + name string + op pbcodec.DBOp_Operation +} + +func (m operationOnTableMatcher) match(value *pbcodec.DBOp) bool { + return m.op == value.Operation && m.name == value.TableName +} + +type indentity struct { +} + +func (i indentity) on(decodedDBOps []*decodedDBOp) []*decodedDBOp { + return decodedDBOps +} + +type actionHandler struct { + projection projection + keyExtractor cel.Program + ceType string + split bool +} + +type ActionGenerator struct { + actions map[string][]actionHandler +} + +func (a actionHandler) evalGeneration(stepName string, + transaction *pbcodec.TransactionTrace, + trace *pbcodec.ActionTrace, + ops []*decodedDBOp) (Generation, error) { + activation, err := NewActivation(stepName, transaction, + trace, + ops, + ) + if err != nil { + return Generation{}, err + } + ceType := a.ceType + + key, err := evalString(a.keyExtractor, activation) + if err != nil { + return Generation{}, fmt.Errorf("action:%s, event keyeval: %w", trace.Action.Name, err) + } + + return Generation{ + key, + ceType, + ops, + }, nil +} + +func (g ActionGenerator) Apply(stepName string, + transaction *pbcodec.TransactionTrace, + trace *pbcodec.ActionTrace, + decodedDBOps []*decodedDBOp, +) ([]Generation, error) { + if actionHandlers, ok := g.actions[trace.Action.Name]; ok { + for _, actionHandler := range actionHandlers { + projection := actionHandler.projection.on(decodedDBOps) + if actionHandler.split { + var generations []Generation = make([]Generation, 0, 1) + for i := range projection { + generation, err := actionHandler.evalGeneration(stepName, transaction, trace, projection[i:i+1]) + if err != nil { + return nil, fmt.Errorf("actionHandler.evalGeneration() error: %w", err) + } + generations = append(generations, generation) + } + return generations, nil + } else { + generation, err := actionHandler.evalGeneration(stepName, transaction, trace, projection) + if err != nil { + return nil, fmt.Errorf("actionHandler.evalGeneration() error: %w", err) + } + return []Generation{generation}, nil + } + } + } + return nil, nil +} + +type ExpressionsGenerator struct { + keyExtractor cel.Program + ceTypeExtractor cel.Program +} + +func (g ExpressionsGenerator) Apply(stepName string, + transaction *pbcodec.TransactionTrace, + trace *pbcodec.ActionTrace, + decodedDBOps []*decodedDBOp, +) ([]Generation, error) { + activation, err := NewActivation(stepName, transaction, + trace, + decodedDBOps, + ) + if err != nil { + return nil, err + } + + eventType, err := evalString(g.ceTypeExtractor, activation) + if err != nil { + return nil, fmt.Errorf("error eventtype eval: %w", err) + } + + eventKeys, err := evalStringArray(g.keyExtractor, activation) + if err != nil { + return nil, fmt.Errorf("event keyeval: %w", err) + } + var generations []Generation = make([]Generation, 0, 1) + dedupeMap := make(map[string]bool) + for _, eventKey := range eventKeys { + if dedupeMap[eventKey] { + continue + } + dedupeMap[eventKey] = true + generations = append(generations, Generation{ + eventKey, + eventType, + decodedDBOps, + }) + } + return generations, nil +} diff --git a/generator_test.go b/generator_test.go new file mode 100644 index 0000000..ab06ead --- /dev/null +++ b/generator_test.go @@ -0,0 +1,795 @@ +package dkafka + +import ( + "context" + "encoding/json" + "fmt" + "path" + "reflect" + "testing" + + pbcodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/codec/v1" + "github.com/eoscanada/eos-go" + "github.com/google/cel-go/cel" +) + +func Test_NewActionGenerator(t *testing.T) { + tests := []struct { + name string + args string + skipKey bool + want Generator + wantErr bool + }{ + { + name: "filter-split", + args: `{"buy":[{"split": true, "filter":"table1","key":"db_ops[0].table_name", "type":"TestEvent"}]}`, + want: ActionGenerator{ + actions: map[string][]actionHandler{ + "buy": {actionHandler{ + projection: filter{ + matcher: tableNameMatcher{"table1"}, + }, + ceType: "TestEvent", + split: true, + }}, + }, + }, + skipKey: true, + wantErr: false, + }, + { + name: "filter", + args: `{"buy":[{"filter":"table1","key":"db_ops[0].table_name", "type":"TestEvent"}]}`, + want: ActionGenerator{ + actions: map[string][]actionHandler{ + "buy": {actionHandler{ + projection: filter{ + matcher: tableNameMatcher{"table1"}, + }, + ceType: "TestEvent", + }}, + }, + }, + skipKey: true, + wantErr: false, + }, + { + name: "identity", + args: `{"buy":[{"key":"db_ops[0].table_name", "type":"TestEvent"}]}`, + want: ActionGenerator{ + actions: map[string][]actionHandler{ + "buy": {actionHandler{ + projection: indentity{}, + ceType: "TestEvent", + }}, + }, + }, + skipKey: true, + wantErr: false, + }, + { + name: "multi-projections", + args: `{"buy":[{"filter":"table1","key":"db_ops[0].table_name", "type":"TestEvent1"},{"filter":"table3","split": true,"key":"db_ops[0].table_name", "type":"TestEvent2"}]}`, + want: ActionGenerator{ + actions: map[string][]actionHandler{ + "buy": { + actionHandler{ + projection: filter{ + matcher: tableNameMatcher{"table1"}, + }, + ceType: "TestEvent1", + }, + actionHandler{ + projection: filter{ + matcher: tableNameMatcher{"table3"}, + }, + ceType: "TestEvent2", + split: true, + }, + }, + }, + }, + skipKey: true, + wantErr: false, + }, + { + name: "multi-actions", + args: `{"buy":[{"filter":"table2","key":"db_ops[0].table_name", "type":"TestEvent1"}],"issue":[{"filter":"table3", "split": true,"key":"db_ops[0].table_name", "type":"TestEvent2"}]}`, + want: ActionGenerator{ + actions: map[string][]actionHandler{ + "buy": { + actionHandler{ + projection: filter{ + matcher: tableNameMatcher{"table2"}, + }, + ceType: "TestEvent1", + }, + }, + "issue": { + actionHandler{ + projection: filter{ + matcher: tableNameMatcher{"table3"}, + }, + ceType: "TestEvent2", + split: true, + }, + }, + }, + }, + skipKey: true, + wantErr: false, + }, + { + name: "missing-key", + args: `{"buy":[{"filter":"table1", "type":"TestEvent"}]}`, + skipKey: false, + wantErr: true, + }, + { + name: "missing-type", + args: `{"buy":[{"filter":"table1","key":"db_ops[0].table_name"}]}`, + skipKey: false, + wantErr: true, + }, + { + name: "too-many-operations", + args: `{"buy":[{"filter":"table1","first":"table2","key":"db_ops[0].table_name", "type":"TestEvent"}]}`, + skipKey: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actionsConf := actionsConfFromJSON(t, tt.args) + if actionsConf == nil { + return + } + got, err := NewActionsGenerator(actionsConf, tt.skipKey) + if (err != nil) != tt.wantErr { + t.Errorf("NewActionGenerator() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewActionGenerator() = %v, want %v", got, tt.want) + } + }) + } +} + +func toJSON(v interface{}) json.RawMessage { + json, err := json.Marshal(v) + if err != nil { + panic(fmt.Sprintf("json.Marshal() error: %v, on: %v", err, v)) + } + return json +} + +func actionsConfFromJSON(t *testing.T, config string) ActionsConf { + actionsConf := make(ActionsConf) + err := json.Unmarshal(json.RawMessage(config), &actionsConf) + if err != nil { + t.Fatalf("Unmarshal error = %v, config: %s", err, config) + return nil + } + return actionsConf +} + +func Test_NewExpressionsGenerator(t *testing.T) { + t.Run("creation", func(t *testing.T) { + got := NewExpressionsGenerator(compileExpr("action"), compileExpr("action")) + _, ok := got.(ExpressionsGenerator) + if !ok { + t.Errorf("NewExpressionsGenerator() does not return a ExpressionsGenerator = %v", got) + } + }) + +} + +func compileExpr(expr string) cel.Program { + program, err := exprToCelProgram(expr) + if err != nil { + panic(fmt.Sprintf("exprToCelProgram error: %v, expr: %s", err, expr)) + } + return program +} + +func Test_ActionGenerator_Apply(t *testing.T) { + eos.LegacyJSON4Asset = false + tests := []struct { + name string + file string + config string + want []Generation + wantErr bool + }{ + { + "indentity", + "testdata/block-30080032.json", + `{"create":[{"key":"transaction_id", "type":"TestType"}]}`, + []Generation{ + { + Key: "a2a53dce154c2ccdca52a981318775938de02f7efef88926ae1d7fd992988530", + CeType: "TestType", + DecodedDBOps: jsonToDBOps(DB_OPS_2), + }, + }, + false, + }, + { + "no-action-matching", + "testdata/block-30080032.json", + `{"issue":[{"key":"transaction_id", "type":"TestType"}]}`, + nil, + false, + }, + { + "action-filter-no-matching", + "testdata/block-30080032.json", + `{"create":[{"filter":"unknown","key":"transaction_id", "type":"TestType"}]}`, + []Generation{ + { + Key: "a2a53dce154c2ccdca52a981318775938de02f7efef88926ae1d7fd992988530", + CeType: "TestType", + }, + }, + false, + }, + { + "action-filter-no-matching-error", + "testdata/block-30080032.json", + `{"create":[{"filter":"unknown","key":"string(db_ops[0].new_json.id)", "type":"TestType"}]}`, + nil, + true, + }, + } + for _, tt := range tests { + t.Run(path.Base(tt.name), func(t *testing.T) { + + byteValue := readFileFromTestdata(t, tt.file) + + block := &pbcodec.Block{} + // must delete rlimit_ops, valid_block_signing_authority_v2, active_schedule_v2 + err := json.Unmarshal(byteValue, block) + if err != nil { + t.Fatalf("Unmarshal() error: %v", err) + } + var localABIFiles = map[string]string{ + "eosio.nft.ft": "testdata/eosio.nft.ft.abi", + } + abiFiles, err := LoadABIFiles(localABIFiles) + if err != nil { + t.Fatalf("LoadABIFiles() error: %v", err) + } + abiDecoder := NewABIDecoder(abiFiles, nil, context.Background()) + actionsConfig := actionsConfFromJSON(t, tt.config) + generator, err := NewActionsGenerator(actionsConfig) + if err != nil { + t.Fatalf("NewActionGenerator() error: %v", err) + } + + trx := block.TransactionTraces()[0] + act := trx.ActionTraces[0] + decodedDBOps, err := abiDecoder.DecodeDBOps(trx.DBOpsForAction(act.ExecutionIndex), block.Number) + if err != nil { + t.Fatalf("DecodeDBOps() error: %v", err) + } + generations, err := generator.Apply("New", trx, act, decodedDBOps) + if (err != nil) != tt.wantErr { + t.Errorf("Apply() error = %v, wantErr %v", err, tt.wantErr) + return + } else if !reflect.DeepEqual(toJSON(generations), toJSON(tt.want)) { + t.Errorf("\nApply() = \n%s\nWant: \n%s", toJSON(generations), toJSON(tt.want)) + } + }) + } +} + +var DB_OPS_2 string = ` +[ + { + "operation": 2, + "code": "eosio.nft.ft", + "table_name": "next.factory", + "primary_key": "next.factory", + "old_payer": "eosio.nft.ft", + "new_payer": "eosio.nft.ft", + "old_data": "GgAAAAAAAAA=", + "new_data": "GwAAAAAAAAA=", + "new_json": { + "value": 27 + }, + "old_json": { + "value": 26 + } + }, + { + "operation": 1, + "code": "eosio.nft.ft", + "scope": "eosio.nft.ft", + "table_name": "factory.a", + "primary_key": "...........1e", + "new_payer": "ultra.nft.ft", + "new_data": "GgAAAAAAAACQF8hrAnNz1JAXyGsCc3PUAAAAAAAAAAAAAAAAAAAAAAAIVVNEAAAAAAJAKB5JiUMD1PQBQNIeron8Qtz0AQAAAAABAAAAAAABAAAAAAGQF8hrAnNz1AABfGh0dHBzOi8vczMudXMtZWFzdC0xLndhc2FiaXN5cy5jb20vdWx0cmFpby11bmlxLXN0YWdpbmcvZDZjOTk5YmJiZDVmM2MxMjJkMjI2MWUyYjc4N2FjYjNlMDNhYWM2YTE4NTIwMjRiOTY1NmM4NWQ3YTk5ZmFiZC56aXDWyZm7vV88Ei0iYeK3h6yz4DqsahhSAkuWVshdepn6vQEoAAAAAAAAAAAAAAA=", + "new_json": { + "asset_creator": "ultra.nft.ft", + "asset_manager": "ultra.nft.ft", + "chosen_rate": [], + "conditionless_receivers": [ + "ultra.nft.ft" + ], + "conversion_rate_oracle_contract": "", + "existing_tokens_no": 0, + "id": 26, + "lockup_time": 0, + "max_mintable_tokens": 40, + "meta_hash": "d6c999bbbd5f3c122d2261e2b787acb3e03aac6a1852024b9656c85d7a99fabd", + "meta_uris": [ + "https://s3.us-east-1.wasabisys.com/ultraio-uniq-staging/d6c999bbbd5f3c122d2261e2b787acb3e03aac6a1852024b9656c85d7a99fabd.zip" + ], + "minimum_resell_price": {"amount":0,"precision":8,"symbol":"USD"}, + "minted_tokens_no": 0, + "recall_window_start": 0, + "resale_shares": [ + { + "basis_point": 500, + "receiver": "uk1ob2ed3so4" + }, + { + "basis_point": 500, + "receiver": "vl1jt2hi3vd4" + } + ], + "stat": 0 + } + } +] +` + +var DB_OPS_NEXT_FACTORY string = ` +[ + { + "operation": 2, + "code": "eosio.nft.ft", + "table_name": "next.factory", + "primary_key": "next.factory", + "old_payer": "eosio.nft.ft", + "new_payer": "eosio.nft.ft", + "old_data": "GgAAAAAAAAA=", + "new_data": "GwAAAAAAAAA=", + "new_json": { + "value": 27 + }, + "old_json": { + "value": 26 + } + } +] +` + +var DB_OPS_FACTORY_A string = ` +[ + { + "operation": 1, + "code": "eosio.nft.ft", + "scope": "eosio.nft.ft", + "table_name": "factory.a", + "primary_key": "...........1e", + "new_payer": "ultra.nft.ft", + "new_data": "GgAAAAAAAACQF8hrAnNz1JAXyGsCc3PUAAAAAAAAAAAAAAAAAAAAAAAIVVNEAAAAAAJAKB5JiUMD1PQBQNIeron8Qtz0AQAAAAABAAAAAAABAAAAAAGQF8hrAnNz1AABfGh0dHBzOi8vczMudXMtZWFzdC0xLndhc2FiaXN5cy5jb20vdWx0cmFpby11bmlxLXN0YWdpbmcvZDZjOTk5YmJiZDVmM2MxMjJkMjI2MWUyYjc4N2FjYjNlMDNhYWM2YTE4NTIwMjRiOTY1NmM4NWQ3YTk5ZmFiZC56aXDWyZm7vV88Ei0iYeK3h6yz4DqsahhSAkuWVshdepn6vQEoAAAAAAAAAAAAAAA=", + "new_json": { + "asset_creator": "ultra.nft.ft", + "asset_manager": "ultra.nft.ft", + "chosen_rate": [], + "conditionless_receivers": [ + "ultra.nft.ft" + ], + "conversion_rate_oracle_contract": "", + "existing_tokens_no": 0, + "id": 26, + "lockup_time": 0, + "max_mintable_tokens": 40, + "meta_hash": "d6c999bbbd5f3c122d2261e2b787acb3e03aac6a1852024b9656c85d7a99fabd", + "meta_uris": [ + "https://s3.us-east-1.wasabisys.com/ultraio-uniq-staging/d6c999bbbd5f3c122d2261e2b787acb3e03aac6a1852024b9656c85d7a99fabd.zip" + ], + "minimum_resell_price": {"amount":0,"precision":8,"symbol":"USD"}, + "minted_tokens_no": 0, + "recall_window_start": 0, + "resale_shares": [ + { + "basis_point": 500, + "receiver": "uk1ob2ed3so4" + }, + { + "basis_point": 500, + "receiver": "vl1jt2hi3vd4" + } + ], + "stat": 0 + } + } +] +` + +var DB_OPS_4 string = ` +[ + { + "operation": 2, + "code": "eosio.nft.ft", + "table_name": "next.factory", + "primary_key": "next.factory", + "old_payer": "eosio.nft.ft", + "new_payer": "eosio.nft.ft", + "old_data": "GgAAAAAAAAA=", + "new_data": "GwAAAAAAAAA=", + "new_json": { + "value": 27 + }, + "old_json": { + "value": 26 + } + }, + { + "operation": 1, + "code": "eosio.nft.ft", + "scope": "eosio.nft.ft", + "table_name": "factory.a", + "primary_key": "...........1e", + "new_payer": "ultra.nft.ft", + "new_data": "GgAAAAAAAACQF8hrAnNz1JAXyGsCc3PUAAAAAAAAAAAAAAAAAAAAAAAIVVNEAAAAAAJAKB5JiUMD1PQBQNIeron8Qtz0AQAAAAABAAAAAAABAAAAAAGQF8hrAnNz1AABfGh0dHBzOi8vczMudXMtZWFzdC0xLndhc2FiaXN5cy5jb20vdWx0cmFpby11bmlxLXN0YWdpbmcvZDZjOTk5YmJiZDVmM2MxMjJkMjI2MWUyYjc4N2FjYjNlMDNhYWM2YTE4NTIwMjRiOTY1NmM4NWQ3YTk5ZmFiZC56aXDWyZm7vV88Ei0iYeK3h6yz4DqsahhSAkuWVshdepn6vQEoAAAAAAAAAAAAAAA=", + "new_json": { + "asset_creator": "ultra.nft.ft", + "asset_manager": "ultra.nft.ft", + "chosen_rate": [], + "conditionless_receivers": [ + "ultra.nft.ft" + ], + "conversion_rate_oracle_contract": "", + "existing_tokens_no": 0, + "id": 26, + "lockup_time": 0, + "max_mintable_tokens": 40, + "meta_hash": "d6c999bbbd5f3c122d2261e2b787acb3e03aac6a1852024b9656c85d7a99fabd", + "meta_uris": [ + "https://s3.us-east-1.wasabisys.com/ultraio-uniq-staging/d6c999bbbd5f3c122d2261e2b787acb3e03aac6a1852024b9656c85d7a99fabd.zip" + ], + "minimum_resell_price": {"amount":0,"precision":8,"symbol":"USD"}, + "minted_tokens_no": 0, + "recall_window_start": 0, + "resale_shares": [ + { + "basis_point": 500, + "receiver": "uk1ob2ed3so4" + }, + { + "basis_point": 500, + "receiver": "vl1jt2hi3vd4" + } + ], + "stat": 0 + } + }, + { + "operation": 2, + "code": "eosio.nft.ft", + "table_name": "next.factory", + "primary_key": "next.factory", + "old_payer": "eosio.nft.ft", + "new_payer": "eosio.nft.ft", + "old_data": "GgAAAAAAAAA=", + "new_data": "GwAAAAAAAAA=", + "new_json": { + "value": 28 + }, + "old_json": { + "value": 27 + } + }, + { + "operation": 1, + "code": "eosio.nft.ft", + "scope": "eosio.nft.ft", + "table_name": "factory.a", + "primary_key": "...........1e", + "new_payer": "ultra.nft.ft", + "new_data": "GgAAAAAAAACQF8hrAnNz1JAXyGsCc3PUAAAAAAAAAAAAAAAAAAAAAAAIVVNEAAAAAAJAKB5JiUMD1PQBQNIeron8Qtz0AQAAAAABAAAAAAABAAAAAAGQF8hrAnNz1AABfGh0dHBzOi8vczMudXMtZWFzdC0xLndhc2FiaXN5cy5jb20vdWx0cmFpby11bmlxLXN0YWdpbmcvZDZjOTk5YmJiZDVmM2MxMjJkMjI2MWUyYjc4N2FjYjNlMDNhYWM2YTE4NTIwMjRiOTY1NmM4NWQ3YTk5ZmFiZC56aXDWyZm7vV88Ei0iYeK3h6yz4DqsahhSAkuWVshdepn6vQEoAAAAAAAAAAAAAAA=", + "new_json": { + "asset_creator": "ultra.nft.ft", + "asset_manager": "ultra.nft.ft", + "chosen_rate": [], + "conditionless_receivers": [ + "ultra.nft.ft" + ], + "conversion_rate_oracle_contract": "", + "existing_tokens_no": 0, + "id": 27, + "lockup_time": 0, + "max_mintable_tokens": 40, + "meta_hash": "d6c999bbbd5f3c122d2261e2b787acb3e03aac6a1852024b9656c85d7a99fabd", + "meta_uris": [ + "https://s3.us-east-1.wasabisys.com/ultraio-uniq-staging/d6c999bbbd5f3c122d2261e2b787acb3e03aac6a1852024b9656c85d7a99fabd.zip" + ], + "minimum_resell_price": {"amount":0,"precision":8,"symbol":"USD"}, + "minted_tokens_no": 0, + "recall_window_start": 0, + "resale_shares": [ + { + "basis_point": 500, + "receiver": "uk1ob2ed3so4" + }, + { + "basis_point": 500, + "receiver": "vl1jt2hi3vd4" + } + ], + "stat": 0 + } + } +] +` + +var DB_OPS_4_FILTER string = ` +[ + { + "operation": 2, + "code": "eosio.nft.ft", + "table_name": "next.factory", + "primary_key": "next.factory", + "old_payer": "eosio.nft.ft", + "new_payer": "eosio.nft.ft", + "old_data": "GgAAAAAAAAA=", + "new_data": "GwAAAAAAAAA=", + "new_json": { + "value": 27 + }, + "old_json": { + "value": 26 + } + }, + { + "operation": 2, + "code": "eosio.nft.ft", + "table_name": "next.factory", + "primary_key": "next.factory", + "old_payer": "eosio.nft.ft", + "new_payer": "eosio.nft.ft", + "old_data": "GgAAAAAAAAA=", + "new_data": "GwAAAAAAAAA=", + "new_json": { + "value": 28 + }, + "old_json": { + "value": 27 + } + } +] +` + +func Test_operation_on(t *testing.T) { + type args struct { + decodedDBOps string + } + tests := []struct { + name string + op func() projection + args args + want string + }{ + { + name: "identity", + op: func() projection { return indentity{} }, + args: args{ + decodedDBOps: DB_OPS_2, + }, + want: DB_OPS_2, + }, + { + name: "filter-first", + op: func() projection { return filter{tableNameMatcher{"next.factory"}} }, + args: args{ + decodedDBOps: DB_OPS_2, + }, + want: DB_OPS_NEXT_FACTORY, + }, + { + name: "filter-last", + op: func() projection { return filter{tableNameMatcher{"factory.a"}} }, + args: args{ + decodedDBOps: DB_OPS_2, + }, + want: DB_OPS_FACTORY_A, + }, + { + name: "filter-multi", + op: func() projection { return filter{tableNameMatcher{"next.factory"}} }, + args: args{ + decodedDBOps: DB_OPS_4, + }, + want: DB_OPS_4_FILTER, + }, + { + name: "first-next.factory", + op: func() projection { return first{tableNameMatcher{"next.factory"}} }, + args: args{ + decodedDBOps: DB_OPS_4, + }, + want: DB_OPS_NEXT_FACTORY, + }, + { + name: "first-factory-a", + op: func() projection { return first{tableNameMatcher{"factory.a"}} }, + args: args{ + decodedDBOps: DB_OPS_4, + }, + want: DB_OPS_FACTORY_A, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + want := jsonToDBOps(tt.want) + if got := tt.op().on(jsonToDBOps(tt.args.decodedDBOps)); !reflect.DeepEqual(toJSON(got), toJSON(want)) { + t.Errorf("projection.on() = %+v\nwant = %+v", string(toJSON(got)), string(toJSON(want))) + } + }) + } +} + +func jsonToDBOp(dbop string) (result *decodedDBOp) { + err := json.Unmarshal(json.RawMessage(dbop), &result) + if err != nil { + panic(fmt.Sprintf("Unmarshal() error: %v, on: %s", err, dbop)) + } + return +} + +func jsonToDBOps(dbop string) (result []*decodedDBOp) { + err := json.Unmarshal(json.RawMessage(dbop), &result) + if err != nil { + panic(fmt.Sprintf("Unmarshal() error: %v, on: %s", err, dbop)) + } + return +} + +var DB_OP string = ` +{ + "operation": 2, + "code": "eosio.nft.ft", + "table_name": "next.factory", + "primary_key": "next.factory", + "old_payer": "eosio.nft.ft", + "new_payer": "eosio.nft.ft", + "old_data": "GgAAAAAAAAA=", + "new_data": "GwAAAAAAAAA=", + "new_json": { + "value": 27 + }, + "old_json": { + "value": 26 + } +} +` + +func Benchmark_matcher_match(b *testing.B) { + tests := []struct { + name string + expression string + }{ + { + "tablename-match", + "next.factory", + }, + { + "tablename-match-op*", + "*:next.factory", + }, + { + "tablename-no-match", + "next.vincent", + }, + { + "operation-match", + "2:*", + }, + { + "operation-match-no", + "0:*", + }, + { + "table-operation-match", + "2:next.factory", + }, + { + "table-operation-no-match-op", + "0:next.factory", + }, + { + "table-operation-no-match-table", + "2:next.factories", + }, + } + dbop := jsonToDBOp(DB_OP) + var value bool + b.Run("wattermark", func(b *testing.B) { + for i := 0; i < b.N; i++ { + value = dbop.TableName == tests[0].expression + } + }) + + for _, tt := range tests { + matcher, err := expressionToMatcher(tt.expression) + if err != nil { + b.Fatalf("expressionToMatcher() error: %v, on: %s", err, tt.expression) + } + b.Run(tt.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + value = matcher.match(dbop.DBOp) + } + }) + } + b.Log(value) +} + +func Test_expressionToMatcher(t *testing.T) { + tests := []struct { + name string + expression string + want matcher + wantErr bool + }{ + { + "short-tablename", + "vincent", + tableNameMatcher{"vincent"}, + false, + }, + { + "tablename", + "*:vincent", + tableNameMatcher{"vincent"}, + false, + }, + { + "operation", + "2:*", + operationMatcher{2}, + false, + }, + { + "operation-out-range-upper", + "4:*", + nil, + true, + }, + { + "operation-out-range-lower", + "-1:*", + nil, + true, + }, + { + "operation-on-table", + "2:vincent", + operationOnTableMatcher{"vincent", 2}, + false, + }, + { + "unknown", + "*:*", + nil, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := expressionToMatcher(tt.expression) + if (err != nil) != tt.wantErr { + t.Errorf("expressionToMatcher() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("expressionToMatcher() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/go.mod b/go.mod index c57aa32..1aaaaa3 100644 --- a/go.mod +++ b/go.mod @@ -1,34 +1,113 @@ module github.com/dfuse-io/dkafka -go 1.14 +go 1.19 require ( github.com/blendle/zapdriver v1.3.1 - github.com/confluentinc/confluent-kafka-go v1.5.2 + github.com/confluentinc/confluent-kafka-go v1.8.2 github.com/dfuse-io/dfuse-eosio v0.9.0-beta9.0.20210812023750-17e5f52111ab github.com/eoscanada/eos-go v0.9.1-0.20210812015252-984fc96878b6 github.com/golang/protobuf v1.5.2 github.com/google/cel-go v0.6.0 - github.com/klauspost/compress v1.11.0 // indirect - github.com/kr/pretty v0.2.1 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/iancoleman/strcase v0.2.0 + github.com/linkedin/goavro/v2 v2.11.0 github.com/prometheus/client_golang v1.11.0 - github.com/smartystreets/assertions v1.0.0 // indirect - github.com/spf13/cobra v1.1.3 + github.com/riferrei/srclient v0.5.4 + github.com/spf13/cobra v1.3.0 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.7.0 - github.com/streamingfast/bstream v0.0.2-0.20210811181043-4c1920a7e3e3 // indirect + github.com/spf13/viper v1.10.1 + github.com/streamingfast/bstream v0.0.2-0.20210901144836-9a626db444c5 github.com/streamingfast/derr v0.0.0-20210811180100-9138d738bcec - github.com/streamingfast/dgrpc v0.0.0-20210811180351-8646818518b2 // indirect - github.com/streamingfast/dlauncher v0.0.0-20210811194929-f06e488e63da // indirect - github.com/streamingfast/logging v0.0.0-20210811175431-f3b44b61606a // indirect - github.com/streamingfast/pbgo v0.0.6-0.20210812023556-e996f9c4fb86 // indirect - github.com/streamingfast/shutter v1.5.0 // indirect - github.com/tidwall/gjson v1.6.7 - go.uber.org/zap v1.17.0 - golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a - google.golang.org/grpc v1.39.1 - gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect + github.com/streamingfast/dgrpc v0.0.0-20210901144702-c57c3701768b + github.com/streamingfast/dlauncher v0.0.0-20210811194929-f06e488e63da + github.com/streamingfast/logging v0.0.0-20210811175431-f3b44b61606a + github.com/streamingfast/opaque v0.0.0-20210811180740-0c01d37ea308 + github.com/streamingfast/pbgo v0.0.6-0.20210820205306-ba5335146052 + github.com/streamingfast/shutter v1.5.0 + go.uber.org/zap v1.21.0 + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 + google.golang.org/grpc v1.44.0 + google.golang.org/protobuf v1.27.1 + gotest.tools v2.2.0+incompatible +) +require ( + cloud.google.com/go v0.100.2 // indirect + cloud.google.com/go/compute v0.1.0 // indirect + cloud.google.com/go/iam v0.2.0 // indirect + cloud.google.com/go/monitoring v1.3.0 // indirect + cloud.google.com/go/storage v1.10.0 // indirect + cloud.google.com/go/trace v1.1.0 // indirect + contrib.go.opencensus.io/exporter/stackdriver v0.13.8 // indirect + contrib.go.opencensus.io/exporter/zipkin v0.1.1 // indirect + github.com/Azure/azure-pipeline-go v0.2.2 // indirect + github.com/Azure/azure-storage-blob-go v0.8.0 // indirect + github.com/antlr/antlr4 v0.0.0-20200503195918-621b933c7a7f // indirect + github.com/aws/aws-sdk-go v1.37.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/go-test/deep v1.1.1 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-cmp v0.5.7 // indirect + github.com/googleapis/gax-go/v2 v2.1.1 // indirect + github.com/gorilla/mux v1.7.3 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/klauspost/compress v1.11.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/logrusorgru/aurora v2.0.3+incompatible // indirect + github.com/magiconair/properties v1.8.5 // indirect + github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/openzipkin/zipkin-go v0.1.6 // indirect + github.com/pelletier/go-toml v1.9.4 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.26.0 // indirect + github.com/prometheus/procfs v0.6.0 // indirect + github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect + github.com/sethvargo/go-retry v0.1.0 // indirect + github.com/spf13/afero v1.6.0 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/streamingfast/dbin v0.0.0-20210809205249-73d5eca35dc5 // indirect + github.com/streamingfast/dmetrics v0.0.0-20210811180524-8494aeb34447 // indirect + github.com/streamingfast/dstore v0.1.1-0.20210811180812-4db13e99cc22 // indirect + github.com/streamingfast/dtracing v0.0.0-20210811175635-d55665d3622a // indirect + github.com/subosito/gotenv v1.2.0 // indirect + github.com/teris-io/shortid v0.0.0-20201117134242-e59966efd125 // indirect + github.com/tidwall/gjson v1.9.3 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + go.opencensus.io v0.23.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect + golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/api v0.67.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00 // indirect + gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect + gopkg.in/ini.v1 v1.66.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) + +replace github.com/eoscanada/eos-go => github.com/ultraio/eos-go v0.9.1-0.20240115062454-24eab71ff7f3 + +replace github.com/dfuse-io/dfuse-eosio v0.9.0-beta9.0.20210812023750-17e5f52111ab => github.com/ultraio/dfuse-eosio v0.9.2-nodeos1.27.6.0.20230721110507-c5118bc8ea4d + +replace github.com/linkedin/goavro/v2 v2.11.0 => ./fork/goavro diff --git a/go.sum b/go.sum index e731475..1aa3161 100644 --- a/go.sum +++ b/go.sum @@ -16,7 +16,6 @@ cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6 cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.60.0 h1:R+tDlceO7Ss+zyvtsdhTxacDyZ1k99xwskQ4FT7ruoM= cloud.google.com/go v0.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eAU= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= @@ -29,8 +28,14 @@ cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0 h1:MjvSkUq8RuAb+2JLDi5VQmmExRJPUQ3JLCWpRB6fmdw= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -38,9 +43,16 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= +cloud.google.com/go/compute v0.1.0 h1:rSUBvAyVwNJ5uQCKNJFMwPtTvJkfN38b6Pvb9zZoqJ8= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/iam v0.2.0 h1:Ouq6qif4mZdXkb3SiFMpxvu0JQJB1Yid9TsZ23N6hg8= +cloud.google.com/go/iam v0.2.0/go.mod h1:BCK88+tmjAwnZYfOSizmKCTSFjJHCa18t3DpdGEY13Y= +cloud.google.com/go/monitoring v1.3.0 h1:hsJjohhLscxGKXFFUj2AdH+m/jkZ3PyDcprmJ7udj2I= +cloud.google.com/go/monitoring v1.3.0/go.mod h1:rJAj7Dv+RCZInqdbE9qo32ZEaXgnumNQ1Yx8dXx8Yhg= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -49,14 +61,12 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy cloud.google.com/go/storage v1.4.0/go.mod h1:ZusYJWlOshgSBGbt6K3GnB3MT3H1xs2id9+TCl4fDBA= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0 h1:86K1Gel7BQ9/WmNWn7dTKMvTLFzwtBe5FNqYbi9X35g= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -contrib.go.opencensus.io/exporter/stackdriver v0.12.6 h1:Y2FTyj0HgOhfjEW6D6ytZNoz1YcPDXmkKr1I478CWKs= +cloud.google.com/go/trace v1.1.0 h1:/qsa2MXJrKcakrEhjhBbptSdqFDf5uVcGY8Y5Hg1JSk= +cloud.google.com/go/trace v1.1.0/go.mod h1:VFa7W7J9rNqTWMZOjosdpOwBTeCtntDt4/6WlaHughU= contrib.go.opencensus.io/exporter/stackdriver v0.12.6/go.mod h1:8x999/OcIPy5ivx/wDiV7Gx4D+VUPODf0mWRGRc5kSk= -contrib.go.opencensus.io/exporter/stackdriver v0.13.4 h1:ksUxwH3OD5sxkjzEqGxNTl+Xjsmu3BnC/300MhSVTSc= -contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= contrib.go.opencensus.io/exporter/stackdriver v0.13.8 h1:lIFYmQsqejvlq+GobFUbC5F0prD5gvhP6r0gWLZRDq4= contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ= contrib.go.opencensus.io/exporter/zipkin v0.1.1 h1:PR+1zWqY8ceXs1qDQQIlgXe+sdiwCf0n32bH4+Epk8g= @@ -67,17 +77,14 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/Azure/azure-pipeline-go v0.2.1 h1:OLBdZJ3yvOn2MezlWvbrBMTEUQC72zAftRZOMdj5HYo= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.2 h1:6oiIS9yaG6XCCzhgAgKFfIWyo4LLCiDhZot6ltoThhY= github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= -github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= github.com/Azure/azure-storage-blob-go v0.8.0 h1:53qhf0Oxa0nOjgbDeeYPUeyiNmafAFEY95rZLK0Tj6o= github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0= github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= github.com/Azure/go-autorest/autorest/adal v0.8.3 h1:O1AGG9Xig71FxdX9HO5pGNyZ7TbSyHaVg+5eJO/jSGw= github.com/Azure/go-autorest/autorest/adal v0.8.3/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= @@ -85,30 +92,26 @@ github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSW github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.3.0 h1:qJumjCaCudz+OcqE9/XtEPfvtOjOmKaui4EOpFI6zZc= github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= github.com/Jeffail/gabs v1.2.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/RoaringBitmap/roaring v0.4.21/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= github.com/ShinyTrinkets/meta-logger v0.2.0/go.mod h1:cY1KnpPfpLIopR+arZXHYVrVGO6AETrhi3HmRGFjU+U= github.com/ShinyTrinkets/overseer v0.3.0/go.mod h1:MeOEZb808w8+F5WLhiWI2VpOvtkWH55oOlTlAqYI1Pk= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/Sytten/logrus-zap-hook v0.1.0/go.mod h1:J0ktevklw/xJNpI2FzfTdJssk4P0vq3K2qzwihJ2gWU= -github.com/VictoriaMetrics/fastcache v1.5.3/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE= github.com/abourget/llerrgroup v0.0.0-20161118145731-75f536392d17/go.mod h1:QukSa1Sim/0R4aRlWdiBdAy+0i1PBfOd1WHpfYM1ngA= github.com/abourget/llerrgroup v0.2.0/go.mod h1:QukSa1Sim/0R4aRlWdiBdAy+0i1PBfOd1WHpfYM1ngA= github.com/abourget/viperbind v0.1.0/go.mod h1:h7Tfbma7wkxoEEGMQQ5LqkbpcyAFHQkXrtx0s+sZt10= @@ -116,7 +119,6 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat6 github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/alecthomas/gometalinter v2.0.11+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk= -github.com/alecthomas/participle v0.2.0/go.mod h1:SW6HZGeZgSIpcUWX3fXpfZhuaWHnmoD5KCVaqSaNTkk= github.com/alecthomas/participle v0.7.1/go.mod h1:HfdmEuwvr12HXQN44HPWXR0lHmVolVYe4dyL6lQ3duY= github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -124,8 +126,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= @@ -134,22 +134,23 @@ github.com/antlr/antlr4 v0.0.0-20190819145818-b43a4c3a8015/go.mod h1:T7PbCXFs94r github.com/antlr/antlr4 v0.0.0-20200503195918-621b933c7a7f h1:0cEys61Sr2hUBEXfNV8eyQP01oZuBgoMeHunebPirK8= github.com/antlr/antlr4 v0.0.0-20200503195918-621b933c7a7f/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y= github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI= -github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/arpitbbhayani/tripod v0.0.0-20170425181942-66807adce3a5/go.mod h1:9xgChnpjP2FRQUGpqO1dpd18BQ36PO+TdhpY1G6F/OY= github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM= github.com/avast/retry-go v2.6.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.22.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.25.43 h1:R5YqHQFIulYVfgRySz9hvBRTWBjudISa+r0C8XQ1ufg= github.com/aws/aws-sdk-go v1.25.43/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.37.0 h1:GzFnhOIsrGyQ69s7VgqtrG2BG8v7X7vwB3Xpbd/DBBk= github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/azer/is-terminal v1.0.0/go.mod h1:5geuIpRQvdv6g/Q1MwXHbmNUlFLg8QcheGk4dZOmxQU= github.com/azer/logger v1.0.0/go.mod h1:iaDID7UeBTyUh31bjGFlLkr87k23z/mHMMLzt6YQQHU= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -158,85 +159,69 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE= github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= -github.com/blevesearch/bleve v1.0.9/go.mod h1:tb04/rbU29clbtNgorgFd8XdJea4x3ybYaOjWKr+UBU= github.com/blevesearch/bleve v1.0.14/go.mod h1:e/LJTr+E7EaoVdkQZTfoz7dt4KoDNvDbLb8MSKuNTLQ= -github.com/blevesearch/blevex v0.0.0-20190916190636-152f0fe5c040/go.mod h1:WH+MU2F4T0VmSdaPX+Wu5GYoZBrYWdOZWSjzvYcDmqQ= github.com/blevesearch/blevex v1.0.0/go.mod h1:2rNVqoG2BZI8t1/P1awgTKnGlx5MP9ZbtEciQaNhswc= github.com/blevesearch/cld2 v0.0.0-20200327141045-8b5f551d37f5/go.mod h1:PN0QNTLs9+j1bKy3d/GB/59wsNBFC4sWLWG3k69lWbc= github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M= github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+7LMvAB5IbSA= github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ= github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs= -github.com/blevesearch/zap/v11 v11.0.9/go.mod h1:47hzinvmY2EvvJruzsSCJpro7so8L1neseaGjrtXHOY= github.com/blevesearch/zap/v11 v11.0.14/go.mod h1:MUEZh6VHGXv1PKx3WnCbdP404LGG2IZVa/L66pyFwnY= -github.com/blevesearch/zap/v12 v12.0.9/go.mod h1:paQuvxy7yXor+0Mx8p2KNmJgygQbQNN+W6HRfL5Hvwc= github.com/blevesearch/zap/v12 v12.0.14/go.mod h1:rOnuZOiMKPQj18AEKEHJxuI14236tTQ1ZJz4PAnWlUg= -github.com/blevesearch/zap/v13 v13.0.1/go.mod h1:XmyNLMvMf8Z5FjLANXwUeDW3e1+o77TTGUWrth7T9WI= github.com/blevesearch/zap/v13 v13.0.6/go.mod h1:L89gsjdRKGyGrRN6nCpIScCvvkyxvmeDCwZRcjjPCrw= -github.com/blevesearch/zap/v14 v14.0.0/go.mod h1:sUc/gPGJlFbSQ2ZUh/wGRYwkKx+Dg/5p+dd+eq6QMXk= github.com/blevesearch/zap/v14 v14.0.5/go.mod h1:bWe8S7tRrSBTIaZ6cLRbgNH4TUDaC9LZSpRGs85AsGY= github.com/blevesearch/zap/v15 v15.0.3/go.mod h1:iuwQrImsh1WjWJ0Ue2kBqY83a0rFtJTqfa9fp1rbVVU= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bronze1man/go-yaml2json v0.0.0-20150129175009-f6f64b738964/go.mod h1:E/+SgS+fFbSMoTIuQxgvi6cvcxmr8v7WGTi2EhNkLy4= -github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.0.1-0.20190104013014-3767db7a7e18/go.mod h1:HD5P3vAIAh+Y2GAxg0PrPN1P8WkepXGpjbUPDHJqqKM= -github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/confluentinc/confluent-kafka-go v1.5.2 h1:l+qt+a0Okmq0Bdr1P55IX4fiwFJyg0lZQmfHkAFkv7E= -github.com/confluentinc/confluent-kafka-go v1.5.2/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/confluentinc/confluent-kafka-go v1.8.2 h1:PBdbvYpyOdFLehj8j+9ba7FL4c4Moxn79gy9cYKxG5E= +github.com/confluentinc/confluent-kafka-go v1.8.2/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.12+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.25+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190212144455-93d5ec2c7f76/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28= -github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/license-bill-of-materials v0.0.0-20190913234955-13baff47494e/go.mod h1:4xMOusJ7xxc84WclVxKT8+lNfGYDwojOUC2OQNCwcj4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k= github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs= -github.com/couchbase/vellum v1.0.1/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4= github.com/couchbase/vellum v1.0.2/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= @@ -245,137 +230,29 @@ github.com/daaku/go.zipexe v1.0.1/go.mod h1:5xWogtqlYnfBXkSB1o9xysukNP9GTvaNkqzU github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= -github.com/dfuse-io/blockmeta v0.0.2-0.20200818234314-2ad05605ed8d/go.mod h1:4L26b3+zMyFs9hAAT5bBy9iMl/7l2DwIpHqP+M0v7Hg= -github.com/dfuse-io/bstream v0.0.2-0.20200714123252-e9115283f55f/go.mod h1:BHEANTxf3ORDdJdGmQ+lJyX8JqjUH45WGBvBKBaMLQg= -github.com/dfuse-io/bstream v0.0.2-0.20200715185351-418dc70e2924/go.mod h1:BHEANTxf3ORDdJdGmQ+lJyX8JqjUH45WGBvBKBaMLQg= -github.com/dfuse-io/bstream v0.0.2-0.20200724152201-64aae5bc532f/go.mod h1:DD1RDqq50aBHDXZLfSv/rNv/35VI+8F7doB8qFecAeM= -github.com/dfuse-io/bstream v0.0.2-0.20200724164826-46514ddda736/go.mod h1:DD1RDqq50aBHDXZLfSv/rNv/35VI+8F7doB8qFecAeM= -github.com/dfuse-io/bstream v0.0.2-0.20200831174806-3a465dd49cb4/go.mod h1:DD1RDqq50aBHDXZLfSv/rNv/35VI+8F7doB8qFecAeM= -github.com/dfuse-io/bstream v0.0.2-0.20210118170643-057893cea2ef/go.mod h1:5Ssiczg5c3tPKh1fF6Yu3n1cPcXlROJ7WEJOqOkIBGM= -github.com/dfuse-io/bstream v0.0.2-0.20210125192647-167e31f99b40 h1:0BqHHddYRR4fIyl1tw3kXoNqd1RG2zKyT3cFFbQt2Rs= -github.com/dfuse-io/bstream v0.0.2-0.20210125192647-167e31f99b40/go.mod h1:rN8qsWxZHlAZaDDwKQZ7l+vvuaNPMl2141lP+cB+kBg= -github.com/dfuse-io/bstream v0.0.2-0.20210810200727-06a4c4aaeb99 h1:HYVvYVoGU/QusLVua1qwsuOkHnazID3fa1casM9hSs0= -github.com/dfuse-io/bstream v0.0.2-0.20210810200727-06a4c4aaeb99/go.mod h1:Xkz3oZjp8ks4Gh0EbTmQlKLvEAf9aRcjMW6LgRQ/t9k= -github.com/dfuse-io/bstream v0.0.2-0.20210811032019-ae285ee33ca3 h1:D0yGHa9mNpPsFOVHlv117KUgkuUuhlbtdo4jZ1QqMBQ= -github.com/dfuse-io/bstream v0.0.2-0.20210811032019-ae285ee33ca3/go.mod h1:comh+ic4P11Uh7O5CHhI1ugaUQsnVrMUW8As9rYnFtk= -github.com/dfuse-io/client-go v0.0.0-20210111154104-a57a0b7a63fc/go.mod h1:o2eBOFkO5d2aB1XbQT7ImIv20QFuv+Yxdn0GBZJa/HU= -github.com/dfuse-io/dauth v0.0.0-20200529171443-21c0e2d262c2/go.mod h1:tab5biQg59hbrHjaXTjaLcsP3zblx6B0I0Keu03xB44= -github.com/dfuse-io/dauth v0.0.0-20200601190857-60bc6a4b4665/go.mod h1:tab5biQg59hbrHjaXTjaLcsP3zblx6B0I0Keu03xB44= -github.com/dfuse-io/dbin v0.0.0-20200406215642-ec7f22e794eb h1:yrE/Ncb9PAjdN7w5Ixx47Oy1kQQdluBfuv7c8D9UaPQ= -github.com/dfuse-io/dbin v0.0.0-20200406215642-ec7f22e794eb/go.mod h1:yMMhO8IdiSS+R/961s9Ac1oyAx3MdAO0OneSZ9Wt/Wg= -github.com/dfuse-io/dbin v0.0.0-20200417174747-9a3806ff5643 h1:EKyGTIJeybbjDLXZf4W3gh+KQZm5XE/nXK/L2RwI0K4= -github.com/dfuse-io/dbin v0.0.0-20200417174747-9a3806ff5643/go.mod h1:yMMhO8IdiSS+R/961s9Ac1oyAx3MdAO0OneSZ9Wt/Wg= github.com/dfuse-io/derr v0.0.0-20200406214256-c690655246a1/go.mod h1:JW/hUKChGd6ytDtvwx4JFo57m1pnFvMxaq9WbDAb2fQ= github.com/dfuse-io/derr v0.0.0-20200417132224-d333cfd0e9a0/go.mod h1:/DjjRCyTi/KIiI1E1hnhvfWwg+K06jo+qM4VcjHvHq4= -github.com/dfuse-io/derr v0.0.0-20200730183817-a747f6f333ad/go.mod h1:/DjjRCyTi/KIiI1E1hnhvfWwg+K06jo+qM4VcjHvHq4= -github.com/dfuse-io/derr v0.0.0-20201001203637-4dc9d8014152/go.mod h1:8bq4KABI5QklH7a16SFIH7/R/jNCOloKu20T5VP+iTk= -github.com/dfuse-io/dfuse-eosio v0.1.1-docker.0.20210128200504-f24b253436ef h1:xrZ9L2SpkMRr16adnQkZD39QUtYOB6Ffe2zUfHU9UcE= -github.com/dfuse-io/dfuse-eosio v0.1.1-docker.0.20210128200504-f24b253436ef/go.mod h1:oJc1enmPQsV4V+3l+OlNgcNWQZh3GakCTHr+W3KI2ZU= -github.com/dfuse-io/dfuse-eosio v0.9.0-beta9.0.20210812023750-17e5f52111ab h1:s5VUQLJ3bAo0tlDONeQlTbveL7w4xXh6OhWaApBsDWs= -github.com/dfuse-io/dfuse-eosio v0.9.0-beta9.0.20210812023750-17e5f52111ab/go.mod h1:DFdnabuEM63joo6lOM6jfKJevb71UfBj5U2MBjMu+a4= -github.com/dfuse-io/dgraphql v0.0.2-0.20201103185948-0b4d17b8db98/go.mod h1:tY4s9vedT5Lk5SV7x71KjfnxSI2holSTLU+defyqgKQ= github.com/dfuse-io/dgrpc v0.0.0-20200406214416-6271093e544c/go.mod h1:n7pQV0mGBMBgJChGKp5gfRLkP2jSCkT+to7fPxKvQPA= -github.com/dfuse-io/dgrpc v0.0.0-20200417124327-c8f215bc4ce5/go.mod h1:n7pQV0mGBMBgJChGKp5gfRLkP2jSCkT+to7fPxKvQPA= -github.com/dfuse-io/dgrpc v0.0.0-20200615163546-b8380f15f7d8/go.mod h1:LJXocN3Yp8Kir8nhx0I65V9Qyw5g/NvXN+Wg7aF5Ujk= -github.com/dfuse-io/dgrpc v0.0.0-20201117184322-6724bb1e1b60/go.mod h1:DANTlPl++VJnjf/wFlLrFQ1GJBSNoieP5808WQJfl9o= -github.com/dfuse-io/dgrpc v0.0.0-20210116004319-046123544d11/go.mod h1:SQUqYjDen2kzhbM+pvNceNtbevla8o7eNX8idtDJDHA= -github.com/dfuse-io/dgrpc v0.0.0-20210118212827-12d9d8a14c40/go.mod h1:SQUqYjDen2kzhbM+pvNceNtbevla8o7eNX8idtDJDHA= -github.com/dfuse-io/dgrpc v0.0.0-20210810041652-d033fee35ae0/go.mod h1:D+ID5BdUilbfULTuX1S6MruUJl/MTf8yrwd8dEXwK3Q= -github.com/dfuse-io/dhammer v0.0.0-20200407040847-5fb1dcc749e1/go.mod h1:7NpM9VIxbrn4UygbhQt735BXs+BktEtv0YrLSXlgDQA= -github.com/dfuse-io/dhammer v0.0.0-20200723173708-b7e52c540f64/go.mod h1:oMDmMjK+thNJi8TgUgqEx5PkpW9GFlGfrWy9Id1zDiA= -github.com/dfuse-io/dipp v1.0.1-0.20200407033930-5c17c531c3c4/go.mod h1:4OSkJC9kCEQMw9f79Kl4Dl2vQZxQLknn0W7hZlZLsPM= -github.com/dfuse-io/dlauncher v0.0.0-20201112212422-91f62bcef971/go.mod h1:AfUE2njp7F5hXASr10ux3dmBbq4iCjFBmz0enYJRrBk= -github.com/dfuse-io/dmesh v0.0.0-20200427143025-f55305fa4b95/go.mod h1:xUkrTebUXPRjO5wm565eEQluow9nzb6NYl42aeupllQ= -github.com/dfuse-io/dmesh v0.0.0-20200602201926-d79e48fdac7c/go.mod h1:e1x/Bvi0Y0Hl5pJCboN4RnNHwGuVqS6kIZFgrAs9V2E= -github.com/dfuse-io/dmesh v0.0.0-20201117184409-6a094bb91fca h1:mbpkMBmoOCLu5xyhRebZMbv4b0fPNOhyk6IEqKkV4Ko= -github.com/dfuse-io/dmesh v0.0.0-20201117184409-6a094bb91fca/go.mod h1:XhSRmGQiR+2/vOVGXpwCqnF1/FDunrZxsdkDjgxAtZs= -github.com/dfuse-io/dmetering v0.0.0-20200529171737-525c3029795c/go.mod h1:Qtr0qJsu5zfOJEKxNhp+hlTB785Psam/iAR2VbMa7uA= -github.com/dfuse-io/dmetering v0.0.0-20210112023524-c3ddadbc0d6a/go.mod h1:QFR4Lo8aueGD5u+0HO2A3Un3dM+1pj6NjxYhLGKSNNI= -github.com/dfuse-io/dmetrics v0.0.0-20200406214800-499fc7b320ab h1:sgm+qkT4EM6q2App/t9h5FoPqsJ4Ooph5zw7BdBpbfU= -github.com/dfuse-io/dmetrics v0.0.0-20200406214800-499fc7b320ab/go.mod h1:bTeE3yXvn/O8f0hw7wOstnUrKTCw9HDzC6aBtldPVRI= -github.com/dfuse-io/dmetrics v0.0.0-20200508152325-93e7e9d576bb/go.mod h1:pbb9dPC3ir8G/h3YNhr/5bq35DwjyevEf5tUQvkax0s= -github.com/dfuse-io/dmetrics v0.0.0-20200508170817-3b8cb01fee68 h1:oj8z15TJAXBwpzcDIv3l40eXS8ssIz93ZPfUq/MFfUo= -github.com/dfuse-io/dmetrics v0.0.0-20200508170817-3b8cb01fee68/go.mod h1:pbb9dPC3ir8G/h3YNhr/5bq35DwjyevEf5tUQvkax0s= -github.com/dfuse-io/dstore v0.1.0 h1:UOPE7XFtVxcJ8dy2K9nf1b76NwDodJcc5fdEBnQB77o= -github.com/dfuse-io/dstore v0.1.0/go.mod h1:1dqWgmsMTFiBTsMw/7FM7AEBZiJm0/3f78EIR58MajI= -github.com/dfuse-io/dstore v0.1.1-0.20200819043022-7cdd92b6d8a9/go.mod h1:1dqWgmsMTFiBTsMw/7FM7AEBZiJm0/3f78EIR58MajI= -github.com/dfuse-io/dstore v0.1.1-0.20200820172424-8e0e519ece2f/go.mod h1:1dqWgmsMTFiBTsMw/7FM7AEBZiJm0/3f78EIR58MajI= -github.com/dfuse-io/dstore v0.1.1-0.20200924172801-712ea810c87b h1:9+Dv/GMGclxKBdI3AONNNlcZjCGrx6PV954ZXUH4Xug= -github.com/dfuse-io/dstore v0.1.1-0.20200924172801-712ea810c87b/go.mod h1:1dqWgmsMTFiBTsMw/7FM7AEBZiJm0/3f78EIR58MajI= -github.com/dfuse-io/dtracing v0.0.0-20200406213603-4b0c0063b125 h1:XvwJj/xDY0TQV1y1MvMBINLK/4RGrhuc2HmHscnRgdM= github.com/dfuse-io/dtracing v0.0.0-20200406213603-4b0c0063b125/go.mod h1:SA/v5q2RIuah2W5uvVldEUwfFprEhGiw66Id0j68rtw= -github.com/dfuse-io/dtracing v0.0.0-20200417133307-c09302668d0c h1:G/qxKHQXz8OGYuyB5fpkLybwlzDRoOFOHfpQehyxNUw= -github.com/dfuse-io/dtracing v0.0.0-20200417133307-c09302668d0c/go.mod h1:SA/v5q2RIuah2W5uvVldEUwfFprEhGiw66Id0j68rtw= github.com/dfuse-io/eosio-boot v0.0.0-20201007140702-70b54b34c7a2/go.mod h1:D5ZOOlvI5OmSwfLHNMdVmQFIH3hDmnk/3N3WHQuxwPc= -github.com/dfuse-io/eosws-go v0.0.0-20200520155921-64414618efaf/go.mod h1:tNAkcl+/JpMfoS5lh1dyTJzAi6Z3qjYuUVNpq8wdKWY= github.com/dfuse-io/eosws-go v0.0.0-20210210152811-b72cc007d60a/go.mod h1:tNAkcl+/JpMfoS5lh1dyTJzAi6Z3qjYuUVNpq8wdKWY= -github.com/dfuse-io/firehose v0.1.1-0.20210118213034-5bdcff6a14a7/go.mod h1:FY+UL1dIu+ZUZXn0kGTKQe2tt7KFB3pKZlU3BLdGIdk= -github.com/dfuse-io/fluxdb v0.0.0-20210104215519-ac781957af01/go.mod h1:YhalQTwlihbnBBA3lRgnFsVyK5WV5uRfOd1rZKv/h20= -github.com/dfuse-io/jsonpb v0.0.0-20200406211248-c5cf83f0e0c0/go.mod h1:Qt4EPDfP8T2d/eN96nonFDEyJDMUD3oa/C8LQBX2OAs= -github.com/dfuse-io/jsonpb v0.0.0-20200602171045-28535c4016a2/go.mod h1:FMug2qe6PdGRIn9e2N1ucA+Rg0FOJOgyf2F0gJJSiSw= -github.com/dfuse-io/jsonpb v0.0.0-20200819202948-831ad3282037 h1:mCUwRuMhFNj9vuf7JO6nkYzyzxXbxnKMW2Ia5tbU5fg= -github.com/dfuse-io/jsonpb v0.0.0-20200819202948-831ad3282037/go.mod h1:MkvDx1uQogPmFnwHkNns+ryqp7/BPx93XtsUTnWYltE= -github.com/dfuse-io/kvdb v0.0.0-20200520211319-cbf4776ac2a3/go.mod h1:0fof33gPhbtGXKNYHZCYxKBWD+aogZreeWisJ5t5buE= -github.com/dfuse-io/kvdb v0.0.2-0.20201013164626-89b668e6bd69/go.mod h1:rHS75h2foCE5RHrWs3d5dSTCVATz3Rm6eHAWA+TncnU= -github.com/dfuse-io/kvdb v0.0.2-0.20201125184722-e565bbbcc32e/go.mod h1:X3CqnReRyUXsj/0qhX98RiMisJOD1C3G58dW0h+4gX0= github.com/dfuse-io/logging v0.0.0-20200406213449-45fc25dc6a8d/go.mod h1:80YyilHcgoqrnoIeeJKgcsOw6Y/0/bQzDO/XzNIrIdM= -github.com/dfuse-io/logging v0.0.0-20200407175011-14021b7a79af h1:eh9e9gPlX+pMYcAPqcC66ESCSSqBVkznxwloijoH9Ic= github.com/dfuse-io/logging v0.0.0-20200407175011-14021b7a79af/go.mod h1:80YyilHcgoqrnoIeeJKgcsOw6Y/0/bQzDO/XzNIrIdM= -github.com/dfuse-io/logging v0.0.0-20200417143534-5e26069a5e39/go.mod h1:80YyilHcgoqrnoIeeJKgcsOw6Y/0/bQzDO/XzNIrIdM= -github.com/dfuse-io/logging v0.0.0-20200908182738-02ce9b245eeb/go.mod h1:80YyilHcgoqrnoIeeJKgcsOw6Y/0/bQzDO/XzNIrIdM= -github.com/dfuse-io/logging v0.0.0-20201005173513-5e47a07b5a56/go.mod h1:V+ED4kT/t/lKtH99JQmKIb0v9WL3VaYkJ36CfHlVECI= -github.com/dfuse-io/logging v0.0.0-20201110202154-26697de88c79/go.mod h1:V+ED4kT/t/lKtH99JQmKIb0v9WL3VaYkJ36CfHlVECI= -github.com/dfuse-io/logging v0.0.0-20201120222745-9988f63324bc/go.mod h1:n0Np6Dr/Yv56l8Rq2wgDZpv4vfjT8qlvY34pb3dcI0k= -github.com/dfuse-io/logging v0.0.0-20201125153217-f29c382faa42 h1:WGgtwEYdpKdgsrabHF1FcjhfPehjgAzB8NGEUK3jhfE= -github.com/dfuse-io/logging v0.0.0-20201125153217-f29c382faa42/go.mod h1:n0Np6Dr/Yv56l8Rq2wgDZpv4vfjT8qlvY34pb3dcI0k= -github.com/dfuse-io/logging v0.0.0-20210109005628-b97a57253f70 h1:CuJS05R9jmNlUK8GOxrEELPbfXm0EuGh/30LjkjN5vo= github.com/dfuse-io/logging v0.0.0-20210109005628-b97a57253f70/go.mod h1:EoK/8RFbMEteaCaz89uessDTnCWjbbcr+DXcBh4el5o= -github.com/dfuse-io/logging v0.0.0-20210518215502-2d920b2ad1f2/go.mod h1:EoK/8RFbMEteaCaz89uessDTnCWjbbcr+DXcBh4el5o= -github.com/dfuse-io/merger v0.0.3-0.20200903134352-cc8471c82c4a/go.mod h1:Dp769aARRjBQ9Ur3K5Rl8cQmB29XWueudFa9JOH2f8Q= -github.com/dfuse-io/node-manager v0.0.2-0.20210116001407-5faa4551f66d/go.mod h1:41xyeLWKtU90QH1uBvfDWAJzZbP+gmDmrxR3Dh5n+gU= -github.com/dfuse-io/opaque v0.0.0-20200407012705-75c4ca372d71/go.mod h1:X6Ib6YZlRUabwo4Elw7u1YOLBEiU93r1MwxL3zwY6RE= -github.com/dfuse-io/opaque v0.0.0-20210108174126-bc02ec905d48 h1:nf8r2ROrv80taXLyyUXXtK2ugnn+x2Qd2nABRZAQAHA= -github.com/dfuse-io/opaque v0.0.0-20210108174126-bc02ec905d48/go.mod h1:wsfF2LBZEXeCgvz8ic4lcDN8NlPw3TiPAWXimHXeswk= -github.com/dfuse-io/pbgo v0.0.6-0.20200407175820-b82ffcb63bf6/go.mod h1:thG/VHT3fqEpuhfxNA7psbzizGgJ3kSCM0rSUZ/LNaQ= -github.com/dfuse-io/pbgo v0.0.6-0.20200602201455-99986ef5a09d/go.mod h1:thG/VHT3fqEpuhfxNA7psbzizGgJ3kSCM0rSUZ/LNaQ= -github.com/dfuse-io/pbgo v0.0.6-0.20200722182828-c2634161d5a3/go.mod h1:dbo6DfMDndlPjbJSTZ7Vw/25oKfKg3zzuia+RseeMkU= -github.com/dfuse-io/pbgo v0.0.6-0.20200819050623-1bfd94a6868d/go.mod h1:dbo6DfMDndlPjbJSTZ7Vw/25oKfKg3zzuia+RseeMkU= github.com/dfuse-io/pbgo v0.0.6-0.20210108215028-712d6889e94a/go.mod h1:dbo6DfMDndlPjbJSTZ7Vw/25oKfKg3zzuia+RseeMkU= -github.com/dfuse-io/pbgo v0.0.6-0.20210125181705-b17235518132 h1:xjkqVo6at6RDdkRGhb3QYZmXIsHq1uq2neMOQprLh/w= -github.com/dfuse-io/pbgo v0.0.6-0.20210125181705-b17235518132/go.mod h1:dbo6DfMDndlPjbJSTZ7Vw/25oKfKg3zzuia+RseeMkU= -github.com/dfuse-io/pbgo v0.0.6-0.20210429181308-d54fc7723ad3 h1:ehPpHr7Fj6Ai/PzL3Owjtga600MRRtRx0TvLv3b0pX4= -github.com/dfuse-io/pbgo v0.0.6-0.20210429181308-d54fc7723ad3/go.mod h1:dbo6DfMDndlPjbJSTZ7Vw/25oKfKg3zzuia+RseeMkU= -github.com/dfuse-io/pbgo v0.0.6-0.20210810190312-2053a3b38779/go.mod h1:wbR0a49W0gWiysxiN44db1DHIgIH9FtTUz7w/0I4UVw= -github.com/dfuse-io/pbgo v0.0.6-0.20210811031924-4e767d6fd138 h1:K4NwAVx10Isaw9jf6mc1kPkbYrXBv0B6ldlsf4r60Qo= -github.com/dfuse-io/pbgo v0.0.6-0.20210811031924-4e767d6fd138/go.mod h1:wbR0a49W0gWiysxiN44db1DHIgIH9FtTUz7w/0I4UVw= -github.com/dfuse-io/relayer v0.0.2-0.20201029161257-ec97edca50d7/go.mod h1:JGO5ezLG44L5B1j8g5oGH9E7RGWkFI5hPGNFcCLYqLQ= -github.com/dfuse-io/search v0.0.2-0.20210118185225-30a29e7e9467/go.mod h1:mKSf+fJmFkum7i3don5KLB15VDF8jGbp1u7lW1CsYnE= -github.com/dfuse-io/search-client v0.0.0-20200602205137-71b300d129d2/go.mod h1:TY7lgsDrNonQ7nUtwGK4En08cuG7eRrV743DZozvehk= -github.com/dfuse-io/shutter v1.4.1-0.20200319040708-c809eec458e6/go.mod h1:2mLf0v+n8J8NI7F6L6avYaRNfa6XECB6CP3WO8rRx88= -github.com/dfuse-io/shutter v1.4.1-0.20200407040739-f908f9ab727f/go.mod h1:DNhYoTwU0YjpH3nixaqytVJ43uTe/4SahfipE221e68= -github.com/dfuse-io/shutter v1.4.1/go.mod h1:/BxCU/ucC6WUyqmLbWpDhznS6Yy1HDblsstEBnTyr+4= -github.com/dfuse-io/validator v0.0.0-20200407012817-82c55c634c7a/go.mod h1:wurvriYPW8gHLl16oUVHPKvVV6xd0x5ivLbny1Fin90= github.com/dgraph-io/badger/v2 v2.0.3/go.mod h1:3KY8+bsP8wI0OEnQJAKpd4wIJW/Mm32yw2j/9FUVnIM= github.com/dgraph-io/ristretto v0.0.2-0.20200115201040-8f368f2f2ab3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -383,36 +260,27 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/eoscanada/eos-go v0.8.5/go.mod h1:RKrm2XzZEZWxSMTRqH5QOyJ1fb/qKEjs2ix1aQl0sk4= -github.com/eoscanada/eos-go v0.8.10-0.20190329223853-84b552870f2a/go.mod h1:RKrm2XzZEZWxSMTRqH5QOyJ1fb/qKEjs2ix1aQl0sk4= -github.com/eoscanada/eos-go v0.9.1-0.20200227221642-1b19518201a1/go.mod h1:6RuJFiRU1figWZ39M33o2cERU2MdL6VllElYLHTZNeo= -github.com/eoscanada/eos-go v0.9.1-0.20200415144303-2adb25bcdeca/go.mod h1:exxz2Fyjqx23FIYF1QlhhhggYZxcbZMGp2H/4h7I34Y= -github.com/eoscanada/eos-go v0.9.1-0.20200506160036-5e090ae689ef/go.mod h1:exxz2Fyjqx23FIYF1QlhhhggYZxcbZMGp2H/4h7I34Y= -github.com/eoscanada/eos-go v0.9.1-0.20200723180508-f68c7571db82/go.mod h1:exxz2Fyjqx23FIYF1QlhhhggYZxcbZMGp2H/4h7I34Y= -github.com/eoscanada/eos-go v0.9.1-0.20210115195118-6d94af7a8501 h1:n+sGjT929wJq5XpjPdwUQX0S8kL/vf8ksggiYcOJMj4= -github.com/eoscanada/eos-go v0.9.1-0.20210115195118-6d94af7a8501/go.mod h1:6rqBwgLY59dZN1xqoA8SoHDal0gpNvh1L1Qa3ib8QsY= -github.com/eoscanada/eos-go v0.9.1-0.20210812015252-984fc96878b6 h1:A7WT4j/+oe+w2+z/L2q7EanNN1wdNNMuYLOjY2i17us= -github.com/eoscanada/eos-go v0.9.1-0.20210812015252-984fc96878b6/go.mod h1:dKlu/HXNPI4I5yD7cITTwzfYLNfVaaFt9Y4VngtnhrY= +github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/eoscanada/eosc v1.4.0/go.mod h1:0I+yYzRUTbIMgQsBjBYyNdE5idtbUMMkUQM4fN5A3P4= -github.com/eoscanada/pitreos v1.1.1-0.20200721154110-fb345999fa39/go.mod h1:s+wqQ06P9Micmjx1ejirBXVxVNiYXfZROIgQeBsAneI= github.com/eoscanada/pitreos v1.1.1-0.20210811185752-fa06394508d0/go.mod h1:0HbaqGK5cygDBBm1RQf9J3TMrtr/vyDC6iDSXxOWMNk= -github.com/ethereum/go-ethereum v1.9.9/go.mod h1:a9TqabFudpDu1nucId+k9S8R9whYaHnGBLKFouA5EAo= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= -github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/frostschutz/go-fibmap v0.0.0-20160825162329-b32c231bfe6a/go.mod h1:lRHaZbkaPTYkvVMU3Wm4lF/4113iwNclbfv/gZsHG+I= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/gavv/httpexpect/v2 v2.0.3/go.mod h1:LAoDcy8I/EXEtKJV6wMEJvOMAZVo0MfEk5u4NfiNQa4= -github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.1-0.20180503022059-e9ed3c6dfb39/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= @@ -428,27 +296,26 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v0.0.0-20180717141946-636bf0302bc9/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -463,7 +330,6 @@ github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+Licev github.com/golang/protobuf v0.0.0-20180814211427-aa810b61a9c7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= @@ -474,22 +340,18 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/cel-go v0.4.1/go.mod h1:F0UncVAXNlNjl/4C8hqGdoV6APmuFpetoMJSLIQLBPU= github.com/google/cel-go v0.6.0 h1:Li+angxmgvzlwDsPuFc1/nbqnq3gc4K/X7NrWjOADFI= @@ -502,15 +364,14 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -518,6 +379,7 @@ github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPg github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -537,18 +399,16 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw= github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= @@ -557,18 +417,14 @@ github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.0.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190717153623-606c73359dba h1:7lrqqTRmT69eItAiBzWclaCQrtuZuJUonSYiww6zROo= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190717153623-606c73359dba/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 h1:THDBEeQ9xZ8JEaCLyLQqXMMdRqNr0QAUJTIkQAUtFjg= github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE= @@ -577,37 +433,51 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.8.1/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.12.1 h1:zCy2xE9ablevUOrUZc3Dl72Dt+ya2FNAvC2yLYMHzi4= github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3/go.mod h1:MZ2ZmwcBpvOoJ22IJsc7va19ZwoheaBk43rKg12SKag= +github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ikawaha/kagome.ipadic v1.1.2/go.mod h1:DPSBbU0czaJhAb/5uKQZHMc9MTVRpDugJfX+HddPHHg= @@ -615,103 +485,98 @@ github.com/imkira/go-interpol v1.0.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/C github.com/improbable-eng/grpc-web v0.12.0/go.mod h1:6hRR09jOEG81ADP5wCQju1z71g6OL4eEvELdran/3cs= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= -github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jarcoal/httpmock v1.2.0 h1:gSvTxxFR/MEMfsGrvRbdfpRUMBStovlSRLw0Ep1bwwc= +github.com/jarcoal/httpmock v1.2.0/go.mod h1:oCoTsnAz4+UoOUIf5lJOWV2QQIW5UoeUI6aM2YnWAZk= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= -github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= -github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.10.2 h1:Znfn6hXZAHaLPNnlqUYRrBSReFHYybslgv4PTiyz6P0= github.com/klauspost/compress v1.10.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.0 h1:wJbzvpYMVGG9iTI9VxpnNZfd4DzMPoCWze3GgSqz8yg= github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/kljensen/snowball v0.6.0/go.mod h1:27N7E8fVU5H68RlUmnWwZCfxgt4POBJfENGMvNRhldw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/linkedin/goavro/v2 v2.9.7/go.mod h1:UgQUb2N/pmueQYH9bfqFioWxzYCZXSfF8Jw03O5sjqA= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= -github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs= -github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= -github.com/lytics/lifecycle v0.0.0-20130117214539-7b4c4028d422 h1:Gumiwx9xUqjh54icRYIYIHDUrtDDrU1oI0VHGZ4/FbI= +github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/lytics/lifecycle v0.0.0-20130117214539-7b4c4028d422/go.mod h1:E8kU8C8dGPj2zMi5ofP4+qGu9FH1tsPdboOTYjUoA0U= -github.com/lytics/ordpool v0.0.0-20130426221837-8d833f097fe7 h1:cfwDIOg9JIYe7Cw6rXEAg84eU9ofYm16e+0LZSt0sE8= github.com/lytics/ordpool v0.0.0-20130426221837-8d833f097fe7/go.mod h1:pQoaXPOjYaumzQC9+0Gug1UeLiWtazaks9kbaFHeFW8= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/manifoldco/promptui v0.3.2/go.mod h1:8JU+igZ+eeiiRku4T5BjtKh2ms8sziGpSYl1gN8Bazw= -github.com/manifoldco/promptui v0.7.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= -github.com/matishsiao/goInfo v0.0.0-20170803142006-617e6440957e/go.mod h1:yLZrFIhv+Z20hxHvcZpEyKVQp9HMsOJkXAxx7yDqtvg= github.com/matishsiao/goInfo v0.0.0-20200404012835-b5f882ee2288/go.mod h1:yLZrFIhv+Z20hxHvcZpEyKVQp9HMsOJkXAxx7yDqtvg= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149 h1:HfxbT6/JcvIljmERptWhwa8XzP7H3T+Z2N26gTsaDaA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d h1:oNAwILwmgWKFpuU+dXvI6dl9jG2mAWAZLX3r9s0PPiw= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/maxatome/go-testdeep v1.11.0/go.mod h1:011SgQ6efzZYAen6fDn4BqQ+lUR72ysdyKe7Dyogw70= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v0.0.0-20180801233206-58046073cbff/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -722,38 +587,31 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.5.0/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= -github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -761,15 +619,14 @@ github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTm github.com/openzipkin/zipkin-go v0.1.6 h1:yXiysv1CSK7Q5yjGy1710zZGnsbMUIjluWBxtLXHPBo= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= -github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/pborman/uuid v0.0.0-20180122190007-c65b2f87fee3/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.2.1-0.20180724185102-c2dbbc24a979 h1:kNmPAP94Bj9I/UwbvxYqfutkyEiltzsaVeYXPBou+qg= github.com/pelletier/go-toml v1.2.1-0.20180724185102-c2dbbc24a979/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8/go.mod h1:B1+S9LNcuMyLH/4HMTViQOJevkGiik3wW2AN9zb2fNQ= github.com/pingcap/check v0.0.0-20200212061837-5e12011dc712/go.mod h1:PYMCGwN0JHjoqGr3HrZoD+b8Tgx8bKnArhSq8YVzUMc= @@ -785,27 +642,25 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/pquerna/ffjson v0.0.0-20181028064349-e517b90714f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI= -github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.1.0 h1:ElTg5tNp4DqfV7UQjDqv2+RJlNzsDtvNAWccbItceIE= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -814,10 +669,9 @@ github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7q github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= @@ -826,39 +680,34 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= -github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/prom2json v1.3.0 h1:BlqrtbT9lLH3ZsOVhXPsHzFrApCTKRifB7gjJuypu6Y= github.com/prometheus/prom2json v1.3.0/go.mod h1:rMN7m0ApCowcoDlypBHlkNbp5eJQf/+1isKykIP5ZnM= -github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= -github.com/robertkrimen/otto v0.0.0-20170205013659-6a77b7cbc37d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= +github.com/riferrei/srclient v0.5.4 h1:dfwyR5u23QF7beuVl2WemUY2KXh5+Sc4DHKyPXBNYuc= +github.com/riferrei/srclient v0.5.4/go.mod h1:vbkLmWcgYa7JgfPvuy/+K8fTS0p1bApqadxrxi/S1MI= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v0.0.0-20170703205827-abc90934186a/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= +github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE= +github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.0.1-0.20180205163309-da645544ed44 h1:tB9NOR21++IjLyVx3/PCPhWMwqGNCMQEH96A6dMZ/gc= github.com/sergi/go-diff v1.0.1-0.20180205163309-da645544ed44/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sethvargo/go-retry v0.1.0 h1:8sPqlWannzcReEcYjHSNw9becsiYudcwTD7CasGjQaI= github.com/sethvargo/go-retry v0.1.0/go.mod h1:JzIOdZqQDNpPkQDmcqgtteAcxFLtYpNF/zJCM1ysDg8= @@ -887,44 +736,38 @@ github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYED github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= -github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spaolacci/murmur3 v1.0.1-0.20190317074736-539464a789e9/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.4-0.20180821161202-6fd8e29b07d8/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.4-0.20190109003409-7547e83b2d85/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= +github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3-0.20180821114517-d929dcbb1086/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -932,96 +775,60 @@ github.com/spf13/viper v1.1.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7Sr github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E= github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= -github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= -github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= -github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= +github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= +github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= +github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= github.com/steveyen/gtreap v0.1.0/go.mod h1:kl/5J7XbrOmlIbYIXdRHDDE5QxHqpk0cmkT7Z4dM9/Y= github.com/streamingfast/blockmeta v0.0.2-0.20210811194956-90dc4202afda/go.mod h1:/bONTQE1KOG7MOiXEg7Mm6lxMfJ94xL9bItkWNKJgO4= -github.com/streamingfast/bstream v0.0.2-0.20210811181043-4c1920a7e3e3 h1:hZwUF3uoZW2xMKUkrGFaXJQ32UygMB7CRRY82Ei6vEk= github.com/streamingfast/bstream v0.0.2-0.20210811181043-4c1920a7e3e3/go.mod h1:0Ucl6SRmqAcXa0vYhSAnX8sscejfPhpwuW5Pgz9Ndp4= +github.com/streamingfast/bstream v0.0.2-0.20210901144836-9a626db444c5 h1:txdYHu+JlMMdhSBOb04CTYvmKtnCVMpzwa0kqS0JbOo= +github.com/streamingfast/bstream v0.0.2-0.20210901144836-9a626db444c5/go.mod h1:OKY4Lu6Y5+MozsctfGZ9b00/WDfOUNTN3SKNoNjT7mE= github.com/streamingfast/cli v0.0.3-0.20210811201236-5c00ec55462d/go.mod h1:8TWHl1oWEu0JNNUz79R7e9dLPeCJhHmAys4g8cl6L/U= github.com/streamingfast/client-go v0.0.0-20210812010037-2ae1ded7ca05/go.mod h1:5+6MFny+dRsJ1hi2I7JIOfa0mFDVUDj3DWohRyIzJQE= github.com/streamingfast/dauth v0.0.0-20210811181149-e8fd545948cc/go.mod h1:wwjRBEA7vZZ5kfy3I4LB5NJhFJWN2gaYgpBEmOnDwD8= github.com/streamingfast/dbin v0.0.0-20210809205249-73d5eca35dc5 h1:m/3aIPNXCwZ9m/dfYdOs8ftrS7GJl82ipVr6K2aZiBs= github.com/streamingfast/dbin v0.0.0-20210809205249-73d5eca35dc5/go.mod h1:YStE7K5/GH47JsWpY7LMKsDaXXpMLU/M26vYFzXHYRk= -github.com/streamingfast/derr v0.0.0-20200406214256-c690655246a1 h1:ejixSugMZ417KvnJhk4cZJu/VW4ql0AlL+seE4jh1Kc= -github.com/streamingfast/derr v0.0.0-20200406214256-c690655246a1/go.mod h1:JW/hUKChGd6ytDtvwx4JFo57m1pnFvMxaq9WbDAb2fQ= -github.com/streamingfast/derr v0.0.0-20200417132224-d333cfd0e9a0/go.mod h1:/DjjRCyTi/KIiI1E1hnhvfWwg+K06jo+qM4VcjHvHq4= -github.com/streamingfast/derr v0.0.0-20200730183817-a747f6f333ad/go.mod h1:/DjjRCyTi/KIiI1E1hnhvfWwg+K06jo+qM4VcjHvHq4= -github.com/streamingfast/derr v0.0.0-20201001203637-4dc9d8014152 h1:8Ee802t3evsUTc7B/W9Lf1pavPje2dtYWEpMtMnE7TY= -github.com/streamingfast/derr v0.0.0-20201001203637-4dc9d8014152/go.mod h1:8bq4KABI5QklH7a16SFIH7/R/jNCOloKu20T5VP+iTk= -github.com/streamingfast/derr v0.0.0-20210810022442-32249850a4fb h1:aZzRC5HbJpQZICZnQDWCZxcbeHYZ8vWGv5yLmWvtaW8= github.com/streamingfast/derr v0.0.0-20210810022442-32249850a4fb/go.mod h1:zL7vriSdKmvbNntPVlXzPDizifaTwUv6+1TGp6KdO88= github.com/streamingfast/derr v0.0.0-20210811180100-9138d738bcec h1:Dst8eCGnbzBVyZqtjC54an4dpTFwVVDqI4ovz0+FH2k= github.com/streamingfast/derr v0.0.0-20210811180100-9138d738bcec/go.mod h1:ulVfui/yGXmPBbt9aAqCWdAjM7YxnZkYHzvQktLfw3M= -github.com/streamingfast/dgraphql v0.0.2-0.20210812004103-ce9976b66675/go.mod h1:By4z/qYHmFmbvra2gQg0XvWgTkQKkNJCAXK/1y4cCGE= -github.com/streamingfast/dgrpc v0.0.0-20200406214416-6271093e544c/go.mod h1:n7pQV0mGBMBgJChGKp5gfRLkP2jSCkT+to7fPxKvQPA= -github.com/streamingfast/dgrpc v0.0.0-20200417124327-c8f215bc4ce5/go.mod h1:n7pQV0mGBMBgJChGKp5gfRLkP2jSCkT+to7fPxKvQPA= -github.com/streamingfast/dgrpc v0.0.0-20200615163546-b8380f15f7d8/go.mod h1:LJXocN3Yp8Kir8nhx0I65V9Qyw5g/NvXN+Wg7aF5Ujk= -github.com/streamingfast/dgrpc v0.0.0-20201117184322-6724bb1e1b60 h1:S2yAbl6AROAy7mp/L3ojf3wO4dHFQ7kn2opBcH9guRg= -github.com/streamingfast/dgrpc v0.0.0-20201117184322-6724bb1e1b60/go.mod h1:DANTlPl++VJnjf/wFlLrFQ1GJBSNoieP5808WQJfl9o= -github.com/streamingfast/dgrpc v0.0.0-20210116004319-046123544d11/go.mod h1:SQUqYjDen2kzhbM+pvNceNtbevla8o7eNX8idtDJDHA= -github.com/streamingfast/dgrpc v0.0.0-20210118212827-12d9d8a14c40 h1:FWYBrMYrL4L/jUKibLwCc2j2MrpUSf4ifQNdXgR2tqE= -github.com/streamingfast/dgrpc v0.0.0-20210118212827-12d9d8a14c40/go.mod h1:SQUqYjDen2kzhbM+pvNceNtbevla8o7eNX8idtDJDHA= -github.com/streamingfast/dgrpc v0.0.0-20210810185305-905172f728e8 h1:K4cFAso8nu8Q7kJSccEB4YWJr+v1k0u/0yOmRC/KRms= +github.com/streamingfast/dgraphql v0.0.2-0.20210908222456-ff1f58e7afcc/go.mod h1:glBDmuKtWOzs6gHYi/U3fWx0pTHjADa4Zgc4EzF6d0s= github.com/streamingfast/dgrpc v0.0.0-20210810185305-905172f728e8/go.mod h1:jBj49IB1Oa9ILev6cx9CfXl7Td83Mdyn2gJOA9F+vZA= -github.com/streamingfast/dgrpc v0.0.0-20210811180351-8646818518b2 h1:2rzOnMX+lcd4SHTkxKOXyL7BVxVLCgjr0mTq9tVpras= github.com/streamingfast/dgrpc v0.0.0-20210811180351-8646818518b2/go.mod h1:WG3YAJ2JU1MSKbl+h+Fc887K4Rht2WEvuLa0eQ2bibE= -github.com/streamingfast/dhammer v0.0.0-20210810183918-d5d2313b80a7/go.mod h1:bl3dwJBjecwQS48W1pd+ljG2M1A9rGcN8XVJ9bb4bLY= +github.com/streamingfast/dgrpc v0.0.0-20210901144702-c57c3701768b h1:FBORjhpkepdgRheKSEWdNch6I48+x4kQ45kmZkJ9pzg= +github.com/streamingfast/dgrpc v0.0.0-20210901144702-c57c3701768b/go.mod h1:8TuRw3ksNsJDYItDUHnydt2IURGxB3s6ME4oSKS8rCs= github.com/streamingfast/dhammer v0.0.0-20210811180702-456c4cf0a840/go.mod h1:XULhFAiYMZG+Zjr+GfVuLhB/yUugoWp7I7z+Q6z5hM4= github.com/streamingfast/dipp v1.0.1-0.20210811200841-d2cca4e058e6/go.mod h1:9ihwWT1EcYrcjohKEWD415EgDgVAbgWKrcERDoVhkGk= -github.com/streamingfast/dlauncher v0.0.0-20201112212422-91f62bcef971 h1:uv+z/fIYGzMD/NYm3O4NC/auPSdahftPhSYmXDMA4sA= -github.com/streamingfast/dlauncher v0.0.0-20201112212422-91f62bcef971/go.mod h1:AfUE2njp7F5hXASr10ux3dmBbq4iCjFBmz0enYJRrBk= -github.com/streamingfast/dlauncher v0.0.0-20210811025343-59aad50e19d6 h1:9s4wXEKd+yJ/GkBJGnreS8dk9V2DMbbcRgM06g4zPHA= -github.com/streamingfast/dlauncher v0.0.0-20210811025343-59aad50e19d6/go.mod h1:59UEiKnzoebzMvL20QJvfX7h7iYXBpoRow2NXYy/mog= github.com/streamingfast/dlauncher v0.0.0-20210811194929-f06e488e63da h1:zyu1Q50z2+I23a9WmJMh89GR5OI0XDzQcNc4n/w/LVU= github.com/streamingfast/dlauncher v0.0.0-20210811194929-f06e488e63da/go.mod h1:cXkcLwW4jmHLDAI1QKqHAlglTfOp/jpoflCJ2N940/E= -github.com/streamingfast/dmesh v0.0.0-20210810205752-f210f374556e/go.mod h1:QFDj0LROGlKw1LPdvNAv8HLcKuY5dua5InaYFFWdlVo= github.com/streamingfast/dmesh v0.0.0-20210811181323-5a37ad73216b/go.mod h1:95l851jWc+56TwR5ebR+CfmQYGb33KRiD47TYqQ1KtU= github.com/streamingfast/dmetering v0.0.0-20210811181351-eef120cfb817/go.mod h1:AP1uR/ywKzFMXA7seSwSR8rJt7YrGupKkYx3HgOrznM= github.com/streamingfast/dmetering v0.0.0-20210812002943-aa53fa1ce172/go.mod h1:HDhLXTm4Dv5Ce7HEnBkQoyIfvijHJFCzjlPXALtEryg= -github.com/streamingfast/dmetrics v0.0.0-20210810172555-564d623b116f h1:IsQnH57dfiyfExvwm8uiQFpQnKS/jLGNQWjXhpYN93U= -github.com/streamingfast/dmetrics v0.0.0-20210810172555-564d623b116f/go.mod h1:PTx7xvHKWacl8ojqV7uIuXqPlpHzpTtyoygIHBEUTDY= -github.com/streamingfast/dmetrics v0.0.0-20210810205551-6071d7bae2cd/go.mod h1:MkZfe6X5Sn3JHLolWeXr66kdocZlUYxY1LJ6fERSRFQ= github.com/streamingfast/dmetrics v0.0.0-20210811180524-8494aeb34447 h1:oZwOVjxpWCqLUjgcPgVigVCHYR40JkmXfm1kuMcCOQk= github.com/streamingfast/dmetrics v0.0.0-20210811180524-8494aeb34447/go.mod h1:VLdQY/FwczmC/flqWkcsBbqXO4BhU4zQDSK7GMrpcjY= -github.com/streamingfast/dstore v0.1.1-0.20210810110932-928f221474e4 h1:zDzSLuihOSd2nmV4zGeYUcCL7zTqK+4zhbwowaKTL9o= -github.com/streamingfast/dstore v0.1.1-0.20210810110932-928f221474e4/go.mod h1:RZSfjj0Odw2Apdm1VsNLIpTC2Ure3sh9Jtpt+Ks8Zuc= github.com/streamingfast/dstore v0.1.1-0.20210811180812-4db13e99cc22 h1:yMnkW8aGD74+qGGudz4706YbgG9OFPgXM7v9UQXqdJk= github.com/streamingfast/dstore v0.1.1-0.20210811180812-4db13e99cc22/go.mod h1:dVDGhFuGT4za66neiB76oi6jPxXH9Rjn3EMYSu+f/Oo= -github.com/streamingfast/dtracing v0.0.0-20210810035915-473055fed32a h1:f2z0hl0EzhIkzErTPGf6ntF1kdOUZTWAz/DajsTgu9s= github.com/streamingfast/dtracing v0.0.0-20210810035915-473055fed32a/go.mod h1:z8WI8Y2sVfFZQQewUC40hE3TgISt3K4n5RwKzXG65Pg= -github.com/streamingfast/dtracing v0.0.0-20210810040633-7c6259bea4a7/go.mod h1:z8WI8Y2sVfFZQQewUC40hE3TgISt3K4n5RwKzXG65Pg= github.com/streamingfast/dtracing v0.0.0-20210811175635-d55665d3622a h1:/7Rw3pYpueJYOQReTJpfAhAPk0uZD4I58LfiUAr4IMc= github.com/streamingfast/dtracing v0.0.0-20210811175635-d55665d3622a/go.mod h1:bqiYZaX6L/MoXNfFQeAdau6g9HLA3yKHkX8KzStt58Q= -github.com/streamingfast/firehose v0.1.1-0.20210811195158-d4b116b4b447/go.mod h1:6eaJVhbrxfEf+LSfr1Bw7bTn5CHXn2Japqv8dQByYEM= +github.com/streamingfast/firehose v0.1.1-0.20210901164748-403e4d029276/go.mod h1:yIN4jdI5i8r5kY5wDUl21ywk/zWqavDJy+uGqmWU8Hs= github.com/streamingfast/fluxdb v0.0.0-20210811195408-0515ef659298/go.mod h1:4nAMvYjDQN0tfnnfBdnSoMCTivXDM4xPdZpROFF7U64= github.com/streamingfast/jsonpb v0.0.0-20210811021341-3670f0aa02d0/go.mod h1:cTNObq2Uofb330y05JbbZZ6RwE6QUXw5iVcHk1Fx3fk= github.com/streamingfast/kvdb v0.0.2-0.20210811194032-09bf862bd2e3/go.mod h1:4dQjd/1ZHhyfAWaFN8cwtdPGZU5LLmAQ7zK2HE5Ok40= github.com/streamingfast/logging v0.0.0-20210811175431-f3b44b61606a h1:Hy7ST3+wGmk4nyBanXKzhRXE63E77UEF93oG51dfq6c= github.com/streamingfast/logging v0.0.0-20210811175431-f3b44b61606a/go.mod h1:4GdqELhZOXj4xwc4IaBmzofzdErGynnaSzuzxy0ZIBo= github.com/streamingfast/merger v0.0.3-0.20210811195536-1011c89f0a67/go.mod h1:AdPD5JmY9Korm14urrQXWbiyq8ojT4Cc5/orgd7kmIE= -github.com/streamingfast/node-manager v0.0.2-0.20210811195732-ccdf9f70dd0b/go.mod h1:XlxhgyID7YUHHTmX3olUb4+/V7eVeMBiV5MOPbu0Y08= -github.com/streamingfast/opaque v0.0.0-20210809210154-b964592beb5d h1:uzybotKG3keKejWLBoMrU0ZmFn3nK1wsoHDCMJ6eSCE= -github.com/streamingfast/opaque v0.0.0-20210809210154-b964592beb5d/go.mod h1:TiogU5+Boyfq0oyvLUd6jy5kAsEZP6r1590Z8IFPhMg= +github.com/streamingfast/node-manager v0.0.2-0.20210830135731-4b00105a1479/go.mod h1:mP/JfeCxmQClAS4JHXO1WrONgSty0OreeXS3kXM2Q6M= github.com/streamingfast/opaque v0.0.0-20210811180740-0c01d37ea308 h1:xlWSfi1BoPfsHtPb0VEHGUcAdBF208LUiFCwfaVPfLA= github.com/streamingfast/opaque v0.0.0-20210811180740-0c01d37ea308/go.mod h1:K1p8Bj/wG34KJvYzPUqtzpndffmpkrVY11u2hkyxCWQ= -github.com/streamingfast/pbgo v0.0.6-0.20210811160400-7c146c2db8cc h1:8tTREe7R7ornjEwjeYpLfkk8AdCN+ZAxPuLp0KPL9WI= github.com/streamingfast/pbgo v0.0.6-0.20210811160400-7c146c2db8cc/go.mod h1:mSi3+mIAffPq/McZ3TqLSmDr8f1v3mi5RHE+zmBHJGk= -github.com/streamingfast/pbgo v0.0.6-0.20210812023556-e996f9c4fb86 h1:zPDLwkGSdW41KqQsGJu4Q5Qs9c+C82JeqY4oULJY0ro= github.com/streamingfast/pbgo v0.0.6-0.20210812023556-e996f9c4fb86/go.mod h1:XKLPtwRtO9JRVfcymM8IIHiJTVIERR+O8uBbdOol3l0= +github.com/streamingfast/pbgo v0.0.6-0.20210820205306-ba5335146052 h1:kjlGNfF3yky5772l7etF3c7k1sbYlYApJHwF6e/r3UA= +github.com/streamingfast/pbgo v0.0.6-0.20210820205306-ba5335146052/go.mod h1:fobYEIfCKwPOY+VLX7HXBVuhrn+iZrzujKrpCgfG3+Q= github.com/streamingfast/relayer v0.0.2-0.20210812020310-adcf15941b23/go.mod h1:ybqMjC0jMhfHvcUaB035Z8sNVLjwcu3KGUrVguG7zB4= github.com/streamingfast/search v0.0.2-0.20210811200310-ec8d3b03e104/go.mod h1:a1f4zBjFCEfFHjsqr9Qy9mEam51UveTvwSI2hasHOLc= github.com/streamingfast/search-client v0.0.0-20210811200417-677bdb765983/go.mod h1:i9Ls2y0YWYM+ChJoqcYlOfIpOgBQER2ksP/orkf8pFk= -github.com/streamingfast/shutter v1.4.1-0.20200319040708-c809eec458e6 h1:Vywa3C2D8QE/QEMinbE61eGW0L9lNPa/x3PLle41ZLc= -github.com/streamingfast/shutter v1.4.1-0.20200319040708-c809eec458e6/go.mod h1:2mLf0v+n8J8NI7F6L6avYaRNfa6XECB6CP3WO8rRx88= -github.com/streamingfast/shutter v1.4.1-0.20200407040739-f908f9ab727f/go.mod h1:DNhYoTwU0YjpH3nixaqytVJ43uTe/4SahfipE221e68= -github.com/streamingfast/shutter v1.4.1 h1:a0HSmmu8vYxR4ynIv1+tH49jrv7gyenNtOJBsmm3OHA= -github.com/streamingfast/shutter v1.4.1/go.mod h1:/BxCU/ucC6WUyqmLbWpDhznS6Yy1HDblsstEBnTyr+4= github.com/streamingfast/shutter v1.5.0 h1:NpzDYzj0HVpSiDJVO/FFSL6QIK/YKOxY0gJAtyaTOgs= github.com/streamingfast/shutter v1.5.0/go.mod h1:B/T6efqdeMGbGwjzPS1ToXzYZI4kDzI5/u4I+7qbjY8= github.com/streamingfast/validator v0.0.0-20210812013448-b9da5752ce14/go.mod h1:t4h97mWfTs6v0zjEFuGDOoW5wLtu9+ttegIx99i7gsM= @@ -1031,8 +838,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= @@ -1041,7 +848,6 @@ github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJ github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tebeka/snowball v0.4.2/go.mod h1:4IfL14h1lvwZcp1sfXuuc7/7yCsvVffTWxWxCLfFpYg= github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= -github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA= github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= github.com/teris-io/shortid v0.0.0-20201117134242-e59966efd125 h1:3SNcvBmEPE1YlB1JpVZouslJpI3GBNoiqW7+wb0Rz7w= github.com/teris-io/shortid v0.0.0-20201117134242-e59966efd125/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= @@ -1049,40 +855,37 @@ github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= github.com/thedevsaddam/govalidator v1.9.6/go.mod h1:Ilx8u7cg5g3LXbSS943cx5kczyNuUn7LH/cK5MYuE90= github.com/thedevsaddam/govalidator v1.9.9/go.mod h1:Ilx8u7cg5g3LXbSS943cx5kczyNuUn7LH/cK5MYuE90= -github.com/tidwall/gjson v1.1.5/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA= github.com/tidwall/gjson v1.2.1/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA= github.com/tidwall/gjson v1.3.2/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= github.com/tidwall/gjson v1.5.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= -github.com/tidwall/gjson v1.6.5/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= -github.com/tidwall/gjson v1.6.7 h1:Mb1M9HZCRWEcXQ8ieJo7auYyyiSux6w9XN3AdTpxJrE= github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= -github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= +github.com/tidwall/gjson v1.9.3 h1:hqzS9wAHMO+KVBBkLxYdkEeeFHuqr95GfClRLKlgK0E= +github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= -github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tidwall/sjson v1.0.3/go.mod h1:bURseu1nuBkFpIES5cz6zBtjmYeOQmEESshn7VpF15Y= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.0.4/go.mod h1:bURseu1nuBkFpIES5cz6zBtjmYeOQmEESshn7VpF15Y= -github.com/tikv/client-go v0.0.0-20200110101306-a3ebdb020c83/go.mod h1:K0NcdVNrXDq92YPLytsrAwRMyuXi7GZCO6dXNH7OzQc= github.com/tikv/client-go v0.0.0-20200824032810-95774393107b/go.mod h1:K0NcdVNrXDq92YPLytsrAwRMyuXi7GZCO6dXNH7OzQc= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYvZ+fpjMXqs+XEriussHjSYqeXVnAdSV1tkMYk= -github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA= +github.com/ultraio/dfuse-eosio v0.9.2-nodeos1.27.6.0.20230721110507-c5118bc8ea4d h1:MLy3ati4tKVOC4auMCRdb61vaa1m8yQ2U2Dm4o26GUg= +github.com/ultraio/dfuse-eosio v0.9.2-nodeos1.27.6.0.20230721110507-c5118bc8ea4d/go.mod h1:iPWSSX4yCjxC/jWJzwhLpQJOyYGprDn3srnsG2map0k= +github.com/ultraio/eos-go v0.9.1-0.20240115062454-24eab71ff7f3 h1:foA0BZ2CbA+FPf65rIyAq6JY6O0oqO/DKruFo2CJqY4= +github.com/ultraio/eos-go v0.9.1-0.20240115062454-24eab71ff7f3/go.mod h1:BCTBH+XkEi+gO38MHRDm0LLcUhaIlekey+SCBaIAJTk= github.com/unrolled/render v1.0.0/go.mod h1:tu82oB5W2ykJRVioYsB+IQKcft7ryBr7w12qMBUPyXg= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.0.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= @@ -1090,11 +893,9 @@ github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= @@ -1107,26 +908,20 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= -go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200225121829-52fba431b686/go.mod h1:VZB9Yx4s43MHItytoe8jcvaEFEgF2QzHDZGfQ/XQjvQ= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200425165423-262c93980547 h1:s71VGheLtWmCYsnNjf+s7XE8HsrZnd3EYGrLGWVm7nY= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200425165423-262c93980547/go.mod h1:YoUyTScD3Vcv2RBm3eGVOq7i1ULiz3OuXoQFWOirmAM= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= -go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0/go.mod h1:tV31atvwzcybuqejDoY3oaNRTtlD2l/Ot78Pc9w7DMY= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= @@ -1136,33 +931,29 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/automaxprocs v1.3.0/go.mod h1:9CWT6lKIep8U41DDaPiH6eFscnTyjfTANNQNx6LrIcA= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.12.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.14.0 h1:/pduUoebOeeJzTDFuoMgC6nRkiasr1sBCIEorly7m4o= go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= -go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= -go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= -go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1175,16 +966,16 @@ golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200406173513-056763e48d71/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1207,10 +998,8 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -1219,12 +1008,11 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1249,7 +1037,6 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1266,8 +1053,6 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= -golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -1277,15 +1062,17 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 h1:a8jGStKg0XqKDlKqjLrXn0ioF5MH36pT7Z0BRTqLhbk= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= @@ -1296,8 +1083,11 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a h1:4Kd8OPUx1xgUwrHDaviWZO8MsgoZTZYC3g+8m16RBww= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1306,7 +1096,6 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1335,14 +1124,14 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1350,7 +1139,9 @@ golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1363,20 +1154,17 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY= -golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1387,11 +1175,22 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1399,16 +1198,15 @@ golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fq golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1429,8 +1227,8 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1463,8 +1261,6 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200806022845-90696ccdc692 h1:fsn47thVa7Ar/TMyXYlZgOoT7M4+kRpb+KpSAqRQx1w= -golang.org/x/tools v0.0.0-20200806022845-90696ccdc692/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -1479,7 +1275,6 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1494,7 +1289,6 @@ google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEt google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= @@ -1505,7 +1299,6 @@ google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0 h1:BaiDisFir8O4IJxvAabCGGkQ6yCJegNQqSVoYUNAnbk= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= @@ -1518,8 +1311,17 @@ google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59t google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.53.0 h1:tR42CQpqOvZcatWtP2TRJdQCQaD0SVxTDIv+vCphrZs= google.golang.org/api v0.53.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0 h1:lYaaLa+x3VVUhtosaK9xihwQ9H9KRa557REHwwZ2orM= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= google.golang.org/appengine v0.0.0-20150527042145-b667a5000b08/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1527,9 +1329,7 @@ google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= @@ -1573,7 +1373,6 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 h1:LCO0fg4kb6WwkXQXRQQgUYsFeFb5taTX5WAx5O/Vt28= google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -1596,8 +1395,25 @@ google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+n google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67 h1:VmMSf20ssFK0+u1dscyTH9bU4/M4y+X/xNfkvD6kGtM= google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00 h1:zmf8Yq9j+IyTpps+paSkmHkSu5fJlRKy69LxRzc17Q0= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v0.0.0-20180607172857-7a6a684ca69e/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= @@ -1608,19 +1424,16 @@ google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= @@ -1631,8 +1444,12 @@ google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1 h1:f37vZbBVTiJ6jKG5mWz8ySOBxNqy6ViPgyhSdVnxF3E= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/examples v0.0.0-20210526223527-2de42fcbbce3/go.mod h1:bF8wuZSAZTcbF7ZPKrDI/qY52toTP/yxLpRRY4Eu9Js= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1644,7 +1461,6 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= @@ -1656,22 +1472,16 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= -gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/olivere/elastic.v3 v3.0.75/go.mod h1:yDEuSnrM51Pc8dM5ov7U8aI/ToR3PG0llA8aRv2qmw0= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/vrecan/death.v3 v3.0.1/go.mod h1:Jy+S9sSCa4cKJF59FMiiDO5/bLCsOtHC8sK3doI1vQM= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1680,14 +1490,13 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1697,7 +1506,6 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= @@ -1711,8 +1519,6 @@ moul.io/http2curl v1.0.1-0.20190925090545-5cd742060b0e/go.mod h1:nejbQVfXh96n9dS rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/inc-59-tables.sh b/inc-59-tables.sh new file mode 100755 index 0000000..ae83343 --- /dev/null +++ b/inc-59-tables.sh @@ -0,0 +1,30 @@ +#!/bin/bash +make clean test build +for i in {1..1000} +do + echo "Run tables $i times" + make forward-stop + make forward + ./build/dkafka cdc tables \ + --dfuse-firehose-grpc-addr=localhost:9000 \ + --abicodec-grpc-addr=localhost:9001 \ + --kafka-endpoints=kafka-roro-361b174f-romain-0052.aivencloud.com:22123 \ + --kafka-ssl-auth=true \ + --kafka-ssl-enable=true \ + --kafka-ssl-ca-file=./ca.pem \ + --kafka-ssl-client-cert-file=./client.crt.pem \ + --kafka-ssl-client-key-file=./client.key.pem \ + --kafka-compression-type=none \ + --kafka-compression-level=-1 \ + --kafka-message-max-bytes=1000000 \ + --delay-between-commits=600s \ + --executed \ + --codec=avro \ + --batch-mode=false \ + --event-source=dkafka-data-tables \ + --table-name=* \ + --start-block-num=0 \ + --kafka-topic=io.dkafka.data.eosio.oracle.tables.v1 \ + --namespace=io.dkafka.data.eosio.oracle.tables.v1 \ + eosio.oracle +done \ No newline at end of file diff --git a/logging.go b/logging.go index 82f8db0..c919cd1 100644 --- a/logging.go +++ b/logging.go @@ -19,8 +19,10 @@ import ( "go.uber.org/zap" ) +var traceEnabled bool var zlog *zap.Logger func init() { logging.Register("github.com/dfuse-io/dkafka", &zlog) + traceEnabled = logging.IsTraceEnabled("dkafka", "github.com/dfuse-io/dkafka") } diff --git a/old.txt b/old.txt new file mode 100644 index 0000000..194c03a --- /dev/null +++ b/old.txt @@ -0,0 +1,55 @@ +goos: linux +goarch: amd64 +pkg: github.com/dfuse-io/dkafka +cpu: AMD Ryzen 7 PRO 5850U with Radeon Graphics +Benchmark_adapter_adapt/filter-out-4 3322484 427.5 ns/op 248 B/op 4 allocs/op +Benchmark_adapter_adapt/filter-out-4 2901050 416.3 ns/op 248 B/op 4 allocs/op +Benchmark_adapter_adapt/filter-out-4 3047149 394.9 ns/op 248 B/op 4 allocs/op +Benchmark_adapter_adapt/filter-out-4 3210727 424.7 ns/op 248 B/op 4 allocs/op +Benchmark_adapter_adapt/filter-out-4 3869222 330.0 ns/op 248 B/op 4 allocs/op +Benchmark_adapter_adapt/filter-out-4 4687104 346.0 ns/op 248 B/op 4 allocs/op +Benchmark_adapter_adapt/filter-out-4 3313744 397.6 ns/op 248 B/op 4 allocs/op +Benchmark_adapter_adapt/filter-in-4 82510 16675 ns/op 6937 B/op 77 allocs/op +Benchmark_adapter_adapt/filter-in-4 83887 16194 ns/op 6936 B/op 77 allocs/op +Benchmark_adapter_adapt/filter-in-4 79270 15736 ns/op 6936 B/op 77 allocs/op +Benchmark_adapter_adapt/filter-in-4 66847 16333 ns/op 6936 B/op 77 allocs/op +Benchmark_adapter_adapt/filter-in-4 74586 15967 ns/op 6938 B/op 77 allocs/op +Benchmark_adapter_adapt/filter-in-4 61003 16991 ns/op 6938 B/op 77 allocs/op +Benchmark_adapter_adapt/filter-in-4 92516 13797 ns/op 6937 B/op 77 allocs/op +Benchmark_adapter_adapt/filter-in-actions-4 21585 56398 ns/op 15039 B/op 171 allocs/op +Benchmark_adapter_adapt/filter-in-actions-4 30547 60593 ns/op 15042 B/op 171 allocs/op +Benchmark_adapter_adapt/filter-in-actions-4 23208 56573 ns/op 15035 B/op 171 allocs/op +Benchmark_adapter_adapt/filter-in-actions-4 18994 63225 ns/op 15039 B/op 171 allocs/op +Benchmark_adapter_adapt/filter-in-actions-4 21715 64556 ns/op 15034 B/op 171 allocs/op +Benchmark_adapter_adapt/filter-in-actions-4 20653 57884 ns/op 15038 B/op 171 allocs/op +Benchmark_adapter_adapt/filter-in-actions-4 23029 57591 ns/op 15037 B/op 171 allocs/op +Benchmark_adapter_adapt/cdc-tables-4 17808 57978 ns/op 18923 B/op 246 allocs/op +Benchmark_adapter_adapt/cdc-tables-4 20086 60973 ns/op 18921 B/op 246 allocs/op +Benchmark_adapter_adapt/cdc-tables-4 21178 58769 ns/op 18923 B/op 246 allocs/op +Benchmark_adapter_adapt/cdc-tables-4 19326 67482 ns/op 18921 B/op 246 allocs/op +Benchmark_adapter_adapt/cdc-tables-4 20247 69410 ns/op 18923 B/op 246 allocs/op +Benchmark_adapter_adapt/cdc-tables-4 17246 77760 ns/op 18921 B/op 246 allocs/op +Benchmark_adapter_adapt/cdc-tables-4 17470 71033 ns/op 18924 B/op 246 allocs/op +Benchmark_adapter_adapt/cdc-actions-4 14523 78289 ns/op 24130 B/op 324 allocs/op +Benchmark_adapter_adapt/cdc-actions-4 28172 76111 ns/op 24131 B/op 324 allocs/op +Benchmark_adapter_adapt/cdc-actions-4 14066 95164 ns/op 24125 B/op 324 allocs/op +Benchmark_adapter_adapt/cdc-actions-4 13239 96527 ns/op 24123 B/op 324 allocs/op +Benchmark_adapter_adapt/cdc-actions-4 14222 87857 ns/op 24127 B/op 324 allocs/op +Benchmark_adapter_adapt/cdc-actions-4 14581 79619 ns/op 24122 B/op 324 allocs/op +Benchmark_adapter_adapt/cdc-actions-4 15501 80672 ns/op 24128 B/op 324 allocs/op +Benchmark_adapter_adapt/cdc-tables-avro-4 40368 30806 ns/op 13120 B/op 138 allocs/op +Benchmark_adapter_adapt/cdc-tables-avro-4 45146 30073 ns/op 13121 B/op 138 allocs/op +Benchmark_adapter_adapt/cdc-tables-avro-4 45459 36507 ns/op 13121 B/op 138 allocs/op +Benchmark_adapter_adapt/cdc-tables-avro-4 47140 25174 ns/op 13122 B/op 138 allocs/op +Benchmark_adapter_adapt/cdc-tables-avro-4 59068 28414 ns/op 13120 B/op 138 allocs/op +Benchmark_adapter_adapt/cdc-tables-avro-4 39205 35518 ns/op 13121 B/op 138 allocs/op +Benchmark_adapter_adapt/cdc-tables-avro-4 34729 36035 ns/op 13120 B/op 138 allocs/op +Benchmark_adapter_adapt/cdc-actions-avro-4 28017 56347 ns/op 16651 B/op 186 allocs/op +Benchmark_adapter_adapt/cdc-actions-avro-4 30774 48193 ns/op 16648 B/op 186 allocs/op +Benchmark_adapter_adapt/cdc-actions-avro-4 25126 53763 ns/op 16648 B/op 186 allocs/op +Benchmark_adapter_adapt/cdc-actions-avro-4 22243 51570 ns/op 16649 B/op 186 allocs/op +Benchmark_adapter_adapt/cdc-actions-avro-4 26901 53403 ns/op 16648 B/op 186 allocs/op +Benchmark_adapter_adapt/cdc-actions-avro-4 30301 49869 ns/op 16651 B/op 186 allocs/op +Benchmark_adapter_adapt/cdc-actions-avro-4 28084 47625 ns/op 16650 B/op 186 allocs/op +PASS +ok github.com/dfuse-io/dkafka 90.398s diff --git a/publisher.go b/publisher.go index 9b20da3..dff8d6e 100644 --- a/publisher.go +++ b/publisher.go @@ -1,24 +1,188 @@ package dkafka import ( - "crypto/sha256" - "encoding/base64" "encoding/json" - "reflect" - "strings" + "time" - "github.com/google/cel-go/cel" + pbcodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/codec/v1" ) -type extension struct { - name string - expr string - prog cel.Program +func newCorrelationRecord() RecordSchema { + return newRecordFQN( + dkafkaNamespace, + "Correlation", + []FieldSchema{ + { + Name: "payer", + Type: "string", + }, + { + Name: "id", + Type: "string", + }, + }, + ) } -var irreversibleOnly = false +func newOptionalCorrelation(correlation *Correlation) map[string]interface{} { + if correlation != nil { + return newCorrelation(correlation.Payer, correlation.Id) + } else { + return nil + } +} + +func newCorrelation(payer string, id string) map[string]interface{} { + return map[string]interface{}{ + "payer": payer, + "id": id, + } +} + +type Correlation struct { + Payer string `json:"payer"` + Id string `json:"id"` +} + +func newDBOpBasic(dbOp *pbcodec.DBOp, dbOpIndex int) map[string]interface{} { + asMap := map[string]interface{}{ + "operation": int32(dbOp.Operation), + "action_index": dbOp.ActionIndex, + "index": dbOpIndex, + } + addOptionalString(&asMap, "code", dbOp.Code) + addOptionalString(&asMap, "scope", dbOp.Scope) + addOptionalString(&asMap, "table_name", dbOp.TableName) + addOptionalString(&asMap, "primary_key", dbOp.PrimaryKey) + addOptionalString(&asMap, "old_payer", dbOp.OldPayer) + addOptionalString(&asMap, "new_payer", dbOp.NewPayer) + addOptionalBytes(&asMap, "old_data", dbOp.OldData) + addOptionalBytes(&asMap, "new_data", dbOp.NewData) + return asMap +} + +func addOptionalBytes(m *map[string]interface{}, key string, value []byte) { + if len(value) > 0 { + (*m)[key] = value + } +} + +func addOptionalString(m *map[string]interface{}, key string, value string) { + if value != "" { + (*m)[key] = value + } +} +func newDBOpBasicSchema() DBOpBasicSchema { + return newRecordFQN( + dkafkaNamespace, + "DBOpBasic", + []FieldSchema{ + NewOptionalField("operation", "int"), + NewOptionalField("action_index", "long"), + NewIntField("index"), + NewOptionalField("code", "string"), + NewOptionalField("scope", "string"), + NewOptionalField("table_name", "string"), + NewOptionalField("primary_key", "string"), + NewOptionalField("old_payer", "string"), + NewOptionalField("new_payer", "string"), + NewOptionalField("old_data", "bytes"), + NewOptionalField("new_data", "bytes"), + }, + ) +} + +func newDBOpInfoRecord(tableName string, jsonData RecordSchema) RecordSchema { + result := newDBOpBasicSchema() + result.Name = tableName + result.Namespace = "" + result.Fields = append(result.Fields, + NewOptionalField("old_json", jsonData), + NewOptionalField("new_json", jsonData.Name), + ) + return result +} -type ActionInfo struct { +func newActionInfoBasic( + account string, + receiver string, + name string, + globalSequence uint64, + authorization []string, + actionOrdinal uint32, + creatorActionOrdinal uint32, + closestUnnotifiedAncestorActionOrdinal uint32, + executionIndex uint32, +) map[string]interface{} { + return map[string]interface{}{ + "account": account, + "receiver": receiver, + "name": name, + "global_seq": globalSequence, + "authorizations": authorization, + "action_ordinal": actionOrdinal, + "creator_action_ordinal": creatorActionOrdinal, + "closest_unnotified_ancestor_action_ordinal": closestUnnotifiedAncestorActionOrdinal, + "execution_index": executionIndex, + } +} + +type ActionInfoBasicSchema = RecordSchema +type DBOpBasicSchema = RecordSchema + +func newActionInfoBasicSchema() ActionInfoBasicSchema { + return newActionInfoBasicSchemaFQN("ActionInfoBasic", dkafkaNamespace) +} +func newActionInfoBasicSchemaN(name string) ActionInfoBasicSchema { + return newActionInfoBasicSchemaFQN(name, "") +} +func newActionInfoBasicSchemaFQN(name string, np string) ActionInfoBasicSchema { + return newRecordFQN( + np, + name, + []FieldSchema{ + { + Name: "account", + Type: "string", + }, + { + Name: "receiver", + Type: "string", + }, + { + Name: "name", + Type: "string", + }, + { + Name: "global_seq", + Type: "long", + }, + { + Name: "authorizations", + Type: NewArray("string"), + }, + { + Name: "action_ordinal", + Type: "long", + }, + { + Name: "creator_action_ordinal", + Type: "long", + }, + { + Name: "closest_unnotified_ancestor_action_ordinal", + Type: "long", + }, + { + Name: "execution_index", + Type: "long", + }, + }, + ) +} + +type ActionInfoDetails struct { + // todo inherit ActionInfoBasic Account string `json:"account"` Receiver string `json:"receiver"` Action string `json:"action"` @@ -29,13 +193,14 @@ type ActionInfo struct { } type event struct { - BlockNum uint32 `json:"block_num"` - BlockID string `json:"block_id"` - Status string `json:"status"` - Executed bool `json:"executed"` - Step string `json:"block_step"` - TransactionID string `json:"trx_id"` - ActionInfo ActionInfo `json:"act_info"` + BlockNum uint32 `json:"block_num"` + BlockID string `json:"block_id"` + Status string `json:"status"` + Executed bool `json:"executed"` + Step string `json:"block_step"` + Correlation *Correlation `json:"correlation,omitempty"` + TransactionID string `json:"trx_id"` + ActionInfo ActionInfoDetails `json:"act_info"` } func (e event) JSON() []byte { @@ -44,42 +209,156 @@ func (e event) JSON() []byte { } -func hashString(data string) []byte { - h := sha256.New() - h.Write([]byte(data)) - return []byte(base64.StdEncoding.EncodeToString(([]byte(h.Sum(nil))))) +func newNotificationContext( + blockID string, + blockNum uint32, + status string, + executed bool, + step string, + transactionID string, + correlation map[string]interface{}, + time time.Time, + cursor string, +) map[string]interface{} { + nc := map[string]interface{}{ + "block_id": blockID, + "block_num": blockNum, + "status": status, + "executed": executed, + "block_step": step, + "trx_id": transactionID, + "time": time, + "cursor": cursor, + } + if len(correlation) > 0 { + nc["correlation"] = correlation + } + return nc } -var stringType = reflect.TypeOf("") -var stringArrayType = reflect.TypeOf([]string{}) +type NotificationContextSchema = RecordSchema + +func newNotificationContextSchema() NotificationContextSchema { + return newRecordFQN( + dkafkaNamespace, + "NotificationContext", + []FieldSchema{ + { + Name: "block_num", + Type: "long", + }, + { + Name: "block_id", + Type: "string", + }, + { + Name: "status", + Type: "string", + }, + { + Name: "executed", + Type: "boolean", + }, + { + Name: "block_step", + Type: "string", + }, + NewOptionalField("correlation", newCorrelationRecord()), + { + Name: "trx_id", + Type: "string", + }, + NewTimestampMillisField("time"), + { + Name: "cursor", + Type: "string", + }, + }, + ) +} -func evalString(prog cel.Program, activation interface{}) (string, error) { - res, _, err := prog.Eval(activation) - if err != nil { - return "", err +func newTableNotification(context map[string]interface{}, action map[string]interface{}, dbOp map[string]interface{}) map[string]interface{} { + return map[string]interface{}{ + "context": context, + "action": action, + "db_op": dbOp, } - out, err := res.ConvertToNative(stringType) - if err != nil { - return "", err +} + +func newTableNotificationSchema(name string, namespace string, ms MetaSupplier, dbOpRecord RecordSchema) MessageSchema { + record := newRecordFQN( + namespace, + name, + []FieldSchema{ + { + Name: "context", + Type: newNotificationContextSchema(), + }, + { + Name: "action", + Type: newActionInfoBasicSchema(), + }, + { + Name: "db_op", + Type: dbOpRecord, + }, + }, + ) + return MessageSchema{ + record, + newMeta(ms), } - return out.(string), nil } -func evalStringArray(prog cel.Program, activation interface{}) ([]string, error) { - res, _, err := prog.Eval(activation) - if err != nil { - return nil, err +func newActionNotification(context map[string]interface{}, actionInfo map[string]interface{}) map[string]interface{} { + return map[string]interface{}{ + "context": context, + "act_info": actionInfo, } - out, err := res.ConvertToNative(stringArrayType) - if err != nil { - return nil, err +} + +func newActionNotificationSchema(name string, namespace string, ms MetaSupplier, actionInfoSchema ActionInfoSchema) MessageSchema { + record := newRecordFQN( + namespace, + name, + []FieldSchema{ + { + Name: "context", + Type: newNotificationContextSchema(), + }, + { + Name: "act_info", + Type: actionInfoSchema, + }, + }, + ) + return MessageSchema{ + record, + newMeta(ms), } - return out.([]string), nil } -func sanitizeStep(step string) string { - return strings.Title(strings.TrimPrefix(step, "STEP_")) +func newActionInfo( + actionInfoBasic map[string]interface{}, + jsonData map[string]interface{}, + dbOps []map[string]interface{}, +) map[string]interface{} { + actionInfoBasic["json_data"] = jsonData + actionInfoBasic["db_ops"] = dbOps + return actionInfoBasic } -func sanitizeStatus(status string) string { - return strings.Title(strings.TrimPrefix(status, "TRANSACTIONSTATUS_")) + +type ActionInfoSchema = RecordSchema + +func newActionInfoSchema(name string, jsonData RecordSchema) ActionInfoSchema { + result := newActionInfoBasicSchemaN(name) + result.Fields = append(result.Fields, + FieldSchema{Name: "json_data", + Type: jsonData, + }, + FieldSchema{Name: "db_ops", + Type: NewArray(newDBOpBasicSchema()), + }, + ) + return result } diff --git a/publisher_test.go b/publisher_test.go new file mode 100644 index 0000000..c20c4c2 --- /dev/null +++ b/publisher_test.go @@ -0,0 +1,61 @@ +package dkafka + +import ( + "reflect" + "testing" +) + +func Test_newDBOpInfoRecord(t *testing.T) { + type args struct { + tableName string + jsonData RecordSchema + } + tests := []struct { + name string + args args + want RecordSchema + }{ + { + name: "default", + args: args{ + tableName: "TableA", + jsonData: newRecordS( + "Toto", + []FieldSchema{ + NewOptionalField("tata", "string"), + }, + ), + }, + want: newRecordS( + "TableA", + []FieldSchema{ + NewOptionalField("operation", "int"), + NewOptionalField("action_index", "long"), + NewIntField("index"), + NewOptionalField("code", "string"), + NewOptionalField("scope", "string"), + NewOptionalField("table_name", "string"), + NewOptionalField("primary_key", "string"), + NewOptionalField("old_payer", "string"), + NewOptionalField("new_payer", "string"), + NewOptionalField("old_data", "bytes"), + NewOptionalField("new_data", "bytes"), + NewOptionalField("old_json", newRecordS( + "Toto", + []FieldSchema{ + NewOptionalField("tata", "string"), + }, + )), + NewOptionalField("new_json", "Toto"), + }, + ), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := newDBOpInfoRecord(tt.args.tableName, tt.args.jsonData); !reflect.DeepEqual(got, tt.want) { + t.Errorf("newDBOpInfoRecord() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/schema.go b/schema.go new file mode 100644 index 0000000..15dbcfc --- /dev/null +++ b/schema.go @@ -0,0 +1,691 @@ +package dkafka + +import ( + "encoding/json" + "fmt" + "math" + "math/big" + "strings" + + "github.com/eoscanada/eos-go" + "github.com/eoscanada/eos-go/ecc" + "github.com/iancoleman/strcase" + "github.com/linkedin/goavro/v2" + "go.uber.org/zap" +) + +const dkafkaNamespace = "io.dkafka" + +type AvroSchemaGenOptions struct { + Action string + Table string + Namespace string + Type string + Version string + AbiSpec *ABI +} + +type ActionSchemaGenOptions struct { + Action string + Namespace string + Version string + AbiSpec *ABI +} + +type NamedSchemaGenOptions struct { + Name string + Namespace string + Version string + AbiSpec *ABI + Domain string +} + +func (o NamedSchemaGenOptions) GetVersion() string { + return o.Version +} +func (o NamedSchemaGenOptions) GetDomain() string { + return o.Domain +} +func (o NamedSchemaGenOptions) GetCompatibility() string { + return "FORWARD" +} +func (o NamedSchemaGenOptions) GetType() string { + return "notification" +} + +func getNamespace(namespace string, abi *ABI) (string, error) { + if namespace == "" { + namespace = strcase.ToDelimited(abi.Account, '.') + } + return checkNamespace(namespace) +} + +func GenerateActionSchema(options NamedSchemaGenOptions) (MessageSchema, error) { + actionCamelCase, ceType := actionCeType(options.Name) + namespace, err := getNamespace(options.Namespace, options.AbiSpec) + if err != nil { + return MessageSchema{}, err + } + actionInfoRecordName := fmt.Sprintf("%sActionInfo", actionCamelCase) + actionParamsRecordName := fmt.Sprintf("%sActionParams", actionCamelCase) + + zlog.Debug( + "generate action avro schema with following names:", + zap.String("namespace", namespace), + zap.String("ce_type", ceType), + zap.String("actionInfo", actionInfoRecordName), + zap.String("actionParams", actionParamsRecordName), + ) + + actionParamsSchema, err := ActionToRecord(options.AbiSpec, eos.ActionName(options.Name)) + if err != nil { + return MessageSchema{}, err + } + actionParamsSchema.Name = actionParamsRecordName + schema := newActionNotificationSchema(ceType, namespace, options, newActionInfoSchema(actionInfoRecordName, actionParamsSchema)) + + return schema, nil +} + +func actionCeType(name string) (actionCamelCase string, ceType string) { + actionCamelCase = strcase.ToCamel(name) + ceType = fmt.Sprintf("%sActionNotification", actionCamelCase) + return +} + +func tableCeType(name string) (tableCamelCase string, ceType string) { + tableCamelCase = strcase.ToCamel(name) + ceType = fmt.Sprintf("%sTableNotification", tableCamelCase) + return +} + +func dbOpRecordName(tableCamelCaseName string) string { + return fmt.Sprintf("%sTableOp", tableCamelCaseName) +} + +func GenerateTableSchema(options NamedSchemaGenOptions) (MessageSchema, error) { + tableCamelCase, ceType := tableCeType(options.Name) + namespace, err := getNamespace(options.Namespace, options.AbiSpec) + if err != nil { + return MessageSchema{}, err + } + dbOpInfoRecordName := fmt.Sprintf("%sTableOpInfo", tableCamelCase) + dbOpRecordName := dbOpRecordName(tableCamelCase) + + zlog.Debug( + "generate table avro schema with following names:", + zap.String("namespace", namespace), + zap.String("ce_type", ceType), + zap.String("TableOpInfo", dbOpInfoRecordName), + zap.String("TableOp", dbOpRecordName), + ) + + dbOpSchema, err := TableToRecord(options.AbiSpec, eos.TableName(options.Name)) + if err != nil { + return MessageSchema{}, err + } + dbOpSchema.Name = dbOpRecordName + dbOpInfoSchema := newDBOpInfoRecord(dbOpInfoRecordName, dbOpSchema) + schema := newTableNotificationSchema(ceType, namespace, options, dbOpInfoSchema) + + return schema, nil +} + +func ActionToRecord(abi *ABI, name eos.ActionName) (RecordSchema, error) { + visited := make(map[string]string) + initBuiltInTypesForActions() + actionDef := abi.ActionForName(name) + if actionDef == nil { + return RecordSchema{}, fmt.Errorf("action '%s' not found", name) + } + + return structToRecord(abi, actionDef.Type, visited) +} + +func TableToRecord(abi *ABI, name eos.TableName) (RecordSchema, error) { + visited := make(map[string]string) + initBuiltInTypesForTables() + tableDef := abi.TableForName(name) + if tableDef == nil { + return RecordSchema{}, fmt.Errorf("table '%s' not found", name) + } + + return structToRecord(abi, tableDef.Type, visited) +} + +func structToRecord(abi *ABI, structName string, visited map[string]string) (RecordSchema, error) { + s := abi.StructForName(structName) + if s == nil { + return RecordSchema{}, fmt.Errorf("struct not found: %s", structName) + } + //inheritance + parentRecord := RecordSchema{} + if s.Base != "" { + var err error + parentRecord, err = structToRecord(abi, s.Base, visited) + if err != nil { + return RecordSchema{}, fmt.Errorf("cannot get parent structToRecord() for %s.%s error: %v", structName, s.Base, err) + } + } + fields, err := abiFieldsToRecordFields(abi, s.Fields, visited) + fields = append(parentRecord.Fields, fields...) + if cap(fields) == 0 { + fields = make([]FieldSchema, 0) + } + if err != nil { + return RecordSchema{}, fmt.Errorf("%s abiFieldsToRecordFields() error: %v", structName, err) + } + return newRecordS( + s.Name, + fields, + ), nil +} + +func abiFieldsToRecordFields(abi *ABI, fieldDefs []eos.FieldDef, visited map[string]string) ([]FieldSchema, error) { + fields := make([]FieldSchema, len(fieldDefs)) + for i, fieldDef := range fieldDefs { + field, err := abiFieldToRecordField(abi, fieldDef, visited) + if err != nil { + return fields, err + } + fields[i] = field + } + return fields, nil +} + +func abiFieldToRecordField(abi *ABI, fieldDef eos.FieldDef, visited map[string]string) (FieldSchema, error) { + zlog.Debug("convert field", zap.String("name", fieldDef.Name), zap.String("type", fieldDef.Type)) + schema, err := resolveFieldTypeSchema(abi, fieldDef.Type, visited) + if err != nil { + return FieldSchema{}, fmt.Errorf("resolve Field type schema error: %v, on field: %s", err, fieldDef.Name) + } + if union, ok := schema.(Union); ok && union[0] == "null" { + return NewNullableField(fieldDef.Name, schema), nil + } else { + return FieldSchema{ + Name: fieldDef.Name, + Type: schema, + }, nil + } +} + +func variantToUnion(abi *ABI, name string, visited map[string]string) (Schema, error) { + v := abi.VariantForName(name) + if v == nil { + return nil, nil + } + if uberVariant, found := hardcodedVariantType[name]; found { + return uberVariant, nil + } + if len(v.Types) == 1 { + //edge case where there is only one type in the union + //then return the type ;) + return resolveType(abi, v.Types[0], visited) + } else { + var union = make([]Schema, len(v.Types)) + for i, aType := range v.Types { + if resolved, err := resolveType(abi, aType, visited); err == nil { + union[i] = resolved + } else { + return nil, err + } + } + return union, nil + } +} + +/* + built_in_types.emplace("bool", pack_unpack()); + built_in_types.emplace("int8", pack_unpack()); + built_in_types.emplace("uint8", pack_unpack()); + built_in_types.emplace("int16", pack_unpack()); + built_in_types.emplace("uint16", pack_unpack()); + built_in_types.emplace("int32", pack_unpack()); + built_in_types.emplace("uint32", pack_unpack()); + built_in_types.emplace("int64", pack_unpack()); + built_in_types.emplace("uint64", pack_unpack()); + built_in_types.emplace("int128", pack_unpack()); + built_in_types.emplace("uint128", pack_unpack()); + built_in_types.emplace("varint32", pack_unpack()); + built_in_types.emplace("varuint32", pack_unpack()); + + // TODO: Add proper support for floating point types. For now this is good enough. + built_in_types.emplace("float32", pack_unpack()); + built_in_types.emplace("float64", pack_unpack()); + built_in_types.emplace("float128", pack_unpack()); + + built_in_types.emplace("time_point", pack_unpack()); + built_in_types.emplace("time_point_sec", pack_unpack()); + built_in_types.emplace("block_timestamp_type", pack_unpack()); + + built_in_types.emplace("name", pack_unpack()); + + built_in_types.emplace("bytes", pack_unpack()); + built_in_types.emplace("string", pack_unpack()); + + built_in_types.emplace("checksum160", pack_unpack()); + built_in_types.emplace("checksum256", pack_unpack()); + built_in_types.emplace("checksum512", pack_unpack()); + + built_in_types.emplace("public_key", pack_unpack_deadline()); + built_in_types.emplace("signature", pack_unpack_deadline()); + + built_in_types.emplace("symbol", pack_unpack()); + built_in_types.emplace("symbol_code", pack_unpack()); + built_in_types.emplace("asset", pack_unpack()); + built_in_types.emplace("extended_asset", pack_unpack()); +*/ + +// var assetSchema RecordSchema = RecordSchema{ +// Namespace: "eosio", +// Name: "Asset", +// Type: "record", +// Doc: "Stores information for owner of asset", +// Convert: "eosio.Asset", +// Fields: []FieldSchema { +// { +// "name": "amount", +// "type": { +// "type": "bytes", +// "logicalType": "decimal", +// "precision": 28, +// "scale": 8, +// } +// }, +// { +// "name": "symbol", +// "type": "string" +// }, +// }, + +// } + +func assetConverter(f func([]byte, interface{}) ([]byte, error)) func([]byte, interface{}) ([]byte, error) { + return func(bytes []byte, value interface{}) ([]byte, error) { + switch valueType := value.(type) { + case eos.Asset: + amount := big.NewRat(int64(valueType.Amount), int64(math.Pow10(int(valueType.Symbol.Precision)))) + return f(bytes, map[string]interface{}{"amount": amount, "symbol": valueType.Symbol.Symbol, "precision": valueType.Symbol.Precision}) + case *eos.Asset: + amount := big.NewRat(int64(valueType.Amount), int64(math.Pow10(int(valueType.Symbol.Precision)))) + return f(bytes, map[string]interface{}{"amount": amount, "symbol": valueType.Symbol.Symbol, "precision": valueType.Symbol.Precision}) + default: + return bytes, fmt.Errorf("unsupported asset type: %T", value) + } + } +} + +func extendedAssetConverter(f func([]byte, any) ([]byte, error)) func([]byte, interface{}) ([]byte, error) { + return func(bytes []byte, value any) ([]byte, error) { + switch valueType := value.(type) { + case eos.ExtendedAsset: + return f(bytes, map[string]interface{}{"quantity": valueType.Asset, "contract": valueType.Contract}) + case *eos.ExtendedAsset: + return f(bytes, map[string]interface{}{"quantity": valueType.Asset, "contract": valueType.Contract}) + default: + return bytes, fmt.Errorf("unsupported extended_asset type: %T", value) + } + } +} + +func publicKeyConverter(f func([]byte, interface{}) ([]byte, error)) func([]byte, interface{}) ([]byte, error) { + return func(bytes []byte, value interface{}) ([]byte, error) { + switch valueType := value.(type) { + case ecc.PublicKey: + return f(bytes, map[string]interface{}{"curve": valueType.Curve, "content": valueType.Content}) + case *ecc.PublicKey: + return f(bytes, map[string]interface{}{"curve": valueType.Curve, "content": valueType.Content}) + default: + return bytes, fmt.Errorf("unsupported public key type type: %T", value) + } + } +} + +func signatureConverter(f func([]byte, interface{}) ([]byte, error)) func([]byte, interface{}) ([]byte, error) { + return func(bytes []byte, value interface{}) ([]byte, error) { + switch valueType := value.(type) { + case ecc.Signature: + return f(bytes, map[string]interface{}{"curve": valueType.Curve, "content": valueType.Content}) + case *ecc.Signature: + return f(bytes, map[string]interface{}{"curve": valueType.Curve, "content": valueType.Content}) + default: + return bytes, fmt.Errorf("unsupported public key type type: %T", value) + } + } +} + +func int128Converter(f func([]byte, interface{}) ([]byte, error)) func([]byte, interface{}) ([]byte, error) { + return func(bytes []byte, value interface{}) ([]byte, error) { + switch valueType := value.(type) { + case eos.Int128: + rat := new(big.Rat).SetInt(valueType.BigInt()) + return f(bytes, rat) + case *eos.Int128: + rat := new(big.Rat).SetInt(valueType.BigInt()) + return f(bytes, rat) + default: + return bytes, fmt.Errorf("unsupported asset type: %T", value) + } + } +} + +func uint128Converter(f func([]byte, interface{}) ([]byte, error)) func([]byte, interface{}) ([]byte, error) { + return func(bytes []byte, value interface{}) ([]byte, error) { + switch valueType := value.(type) { + case eos.Uint128: + rat := new(big.Rat).SetInt(valueType.BigInt()) + return f(bytes, rat) + case *eos.Uint128: + rat := new(big.Rat).SetInt(valueType.BigInt()) + return f(bytes, rat) + default: + return bytes, fmt.Errorf("unsupported asset type: %T", value) + } + } +} + +func symbolConverter(f func([]byte, interface{}) ([]byte, error)) func([]byte, interface{}) ([]byte, error) { + return func(bytes []byte, value interface{}) ([]byte, error) { + switch valueType := value.(type) { + case eos.Symbol: + return f(bytes, valueType.Symbol) + case *eos.Symbol: + return f(bytes, valueType.Symbol) + default: + return bytes, fmt.Errorf("unsupported symbol type: %T", value) + } + } +} + +var schemaTypeConverters = map[string]goavro.ConvertBuild{ + "eosio.Asset": assetConverter, + "eosio.ExtendedAsset": extendedAssetConverter, + "ecc.PublicKey": publicKeyConverter, + "ecc.Signature": signatureConverter, + "eos.Int128": int128Converter, + "eos.Uint128": uint128Converter, + "eos.Symbol": symbolConverter, +} + +var avroPrimitiveTypeByBuiltInTypes map[string]TypedSchema +var avroDecimalLogicalTypeByBuiltInTypes map[string]DecimalLogicalType + +type BuiltInRecordTypeGenerator func(map[string]string) RecordSchema + +var avroRecordTypeByBuiltInTypes map[string]BuiltInRecordTypeGenerator + +var assetSchema RecordSchema = RecordSchema{ + Type: "record", + Name: "Asset", + Namespace: "eosio", + Convert: "eosio.Asset", + Fields: []FieldSchema{ + { + Name: "amount", + Type: json.RawMessage(`{ + "type": "bytes", + "logicalType": "decimal", + "precision": 32, + "scale": 8 + }`), + }, + { + Name: "symbol", + Type: "string", + }, + { + Name: "precision", + Type: "int", + }, + }, +} + +func assetSchemaGenerator(visited map[string]string) RecordSchema { + return assetSchema +} + +func extendedAssetSchemaGenerator(visited map[string]string) RecordSchema { + var assetType Schema = assetSchema + if record, found := visited["asset"]; found { + assetType = record + } + visited["asset"] = reference(assetSchema) + var extendedAssetSchema RecordSchema = RecordSchema{ + Type: "record", + Name: "ExtendedAsset", + Namespace: "eosio", + Convert: "eosio.ExtendedAsset", + Fields: []FieldSchema{ + { + Name: "quantity", + Type: assetType, + }, + { + Name: "contract", + Type: "string", + }, + }, + } + return extendedAssetSchema +} + +var publicKeySchema RecordSchema = RecordSchema{ + Type: "record", + Name: "PublicKey", + Namespace: "ecc", + Convert: "ecc.PublicKey", + Fields: []FieldSchema{ + { + Name: "curve", + Type: "int", + }, + { + Name: "content", + Type: "bytes", + }, + }, +} + +func publicKeySchemaGenerator(visited map[string]string) RecordSchema { + return publicKeySchema +} + +var signatureSchema RecordSchema = RecordSchema{ + Type: "record", + Name: "Signature", + Namespace: "ecc", + Convert: "ecc.Signature", + Fields: []FieldSchema{ + { + Name: "curve", + Type: "int", + }, + { + Name: "content", + Type: "bytes", + }, + }, +} + +func signatureSchemaGenerator(visited map[string]string) RecordSchema { + return signatureSchema +} + +const ( + HardcodedUberVariant = "variant_int8_int16_int32_int64_uint8_uint16_uint32_uint64_float32_float64_string_INT8_VEC_INT16_VEC_INT32_VEC_INT64_VEC_UINT8_VEC_UINT16_VEC_UINT32_VEC_UINT64_VEC_FLOAT32_VEC_FLOAT64_VEC_STRING_VEC" +) + +var hardcodedVariantType map[string]interface{} + +// "uint128": "", +// "float128", +// "extended_asset", + +var allPrimitiveTypes = []TypedSchema{ + { + Type: "int", + }, + { + Type: "long", + }, + { + Type: "float", + }, + { + Type: "double", + }, + { + Type: "string", + }, + { + Type: "bytes", + }, + { + Type: "boolean", + }, +} + +func convertAllPrimitiveTypeToInterface() []interface{} { + converted := make([]interface{}, len(allPrimitiveTypes)) + for i, v := range allPrimitiveTypes { + converted[i] = v + } + return converted +} + +func initBuiltInTypesForTables() { + avroPrimitiveTypeByBuiltInTypes = map[string]TypedSchema{ + "bool": {Type: "boolean", EosType: "bool"}, + "int8": {Type: "int", EosType: "int8"}, + "uint8": {Type: "int", EosType: "uint8"}, + "int16": {Type: "int", EosType: "int16"}, + "uint16": {Type: "int", EosType: "uint16"}, + "int32": {Type: "int", EosType: "int32"}, + "uint32": {Type: "long", EosType: "uint32"}, + "int64": {Type: "long", EosType: "int64"}, + // FIXME: remove the logicalType + "uint64": {Type: "long", EosType: "uint64", LogicalType: "eos.uint64"}, // FIXME maybe use Decimal here see goavro or FIXED + "varint32": {Type: "int", EosType: "varint32"}, + "varuint32": {Type: "long", EosType: "varuint32"}, + "float32": {Type: "float", EosType: "float32"}, + "float64": {Type: "double", EosType: "float64"}, + "time_point": NewTimestampMillisType("time_point"), // fork/eos-go/abidecoder.go TODO add ABI.nativeTime bool to skip time to string conversion in abidecoder read method + "time_point_sec": NewTimestampMillisType("time_point_sec"), // fork/eos-go/abidecoder.go + "block_timestamp_type": NewTimestampMillisType("block_timestamp_type"), // fork/eos-go/abidecoder.go + "name": {Type: "string", EosType: "name"}, + "bytes": {Type: "bytes", EosType: "bytes"}, + "string": {Type: "string", EosType: "string"}, + "checksum160": {Type: "bytes", EosType: "checksum160"}, + "checksum256": {Type: "bytes", EosType: "checksum256"}, + "checksum512": {Type: "bytes", EosType: "checksum512"}, + "symbol": NewSymbolType(), + "symbol_code": {Type: "string", EosType: "symbol_code"}, // FIXME check with blockchain team + } + avroDecimalLogicalTypeByBuiltInTypes = map[string]DecimalLogicalType{ + "int128": NewInt128Type(), + "uint128": NewUint128Type(), + } + avroRecordTypeByBuiltInTypes = map[string]BuiltInRecordTypeGenerator{ + "asset": assetSchemaGenerator, + "extended_asset": extendedAssetSchemaGenerator, + "public_key": publicKeySchemaGenerator, + "signature": signatureSchemaGenerator, + } + hardcodedVariantType = map[string]interface{}{ + HardcodedUberVariant: append(convertAllPrimitiveTypeToInterface(), NewArray(allPrimitiveTypes)), + } +} + +// initBuiltInTypesForActions must rewrite the default types provided by initBuiltInTypesForTables +// because the action details is sent directly in json from the firehouse and the firehouse use the +// string representation of most of the advance type like asset and time based. +// FIXME Can be fixed by using the raw_data of the action trace ;) +func initBuiltInTypesForActions() { + initBuiltInTypesForTables() + avroPrimitiveTypeByBuiltInTypes["asset"] = TypedSchema{Type: "string", EosType: "asset"} + avroPrimitiveTypeByBuiltInTypes["public_key"] = TypedSchema{Type: "string", EosType: "public_key"} + avroPrimitiveTypeByBuiltInTypes["signature"] = TypedSchema{Type: "string", EosType: "signature"} + avroPrimitiveTypeByBuiltInTypes["time_point"] = TypedSchema{Type: "string", EosType: "time_point"} + avroPrimitiveTypeByBuiltInTypes["time_point_sec"] = TypedSchema{Type: "string", EosType: "time_point_sec"} + avroPrimitiveTypeByBuiltInTypes["block_timestamp_type"] = TypedSchema{Type: "string", EosType: "block_timestamp_type"} + avroPrimitiveTypeByBuiltInTypes["symbol"] = TypedSchema{Type: "string", EosType: "symbol"} + avroRecordTypeByBuiltInTypes = map[string]BuiltInRecordTypeGenerator{} +} + +func resolveFieldTypeSchema(abi *ABI, fieldType string, visited map[string]string) (Schema, error) { + zlog.Debug("resolve", zap.String("type", fieldType)) + // remove binary extension marker if any + var isBinaryExtension bool + if elementType := strings.TrimSuffix(fieldType, "$"); elementType != fieldType { + zlog.Debug("binary extension of", zap.String("element", elementType)) + fieldType = elementType + isBinaryExtension = true + } + if elementType := strings.TrimSuffix(fieldType, "[]"); elementType != fieldType { + // todo array + zlog.Debug("array of", zap.String("element", elementType)) + itemType, err := resolveFieldTypeSchema(abi, elementType, visited) + if err != nil { + return nil, fmt.Errorf("type %s not found, error: %w", elementType, err) + } + return NewArray(itemType), nil + } + if optionalType := strings.TrimSuffix(fieldType, "?"); optionalType != fieldType || isBinaryExtension { + zlog.Debug("optional of", zap.String("type", optionalType)) + oType, err := resolveFieldTypeSchema(abi, optionalType, visited) + if err != nil { + return nil, fmt.Errorf("type %s not found, error: %w", optionalType, err) + } + return NewOptional(oType), nil + } + s, err := resolveType(abi, fieldType, visited) + if err != nil { + return nil, fmt.Errorf("unknown type: %s, error: %v", fieldType, err) + } + return s, nil +} + +func resolveType(abi *ABI, name string, visited map[string]string) (Schema, error) { + zlog.Debug("find type", zap.String("name", name)) + if primitive, found := avroPrimitiveTypeByBuiltInTypes[name]; found { + return primitive, nil + } + if decimal, found := avroDecimalLogicalTypeByBuiltInTypes[name]; found { + return decimal, nil + } + // resolve from types + if alias, found := abi.TypeNameForNewTypeName(name); found { + return resolveFieldTypeSchema(abi, alias, visited) + } + // resolve from structs + if referenceName, found := visited[name]; found { + return referenceName, nil + } + + if recordGenerator, found := avroRecordTypeByBuiltInTypes[name]; found { + record := recordGenerator(visited) + visited[name] = reference(record) + return record, nil + } + union, er := variantToUnion(abi, name, visited) + if union != nil && er == nil { + return union, nil + } else if er != nil { + return union, er + } + + if record, err := structToRecord(abi, name, visited); err == nil { + visited[name] = reference(record) + return record, nil + } + + return nil, fmt.Errorf("type not found: %s", name) +} + +func reference(record RecordSchema) string { + if record.Namespace != "" { + return fmt.Sprintf("%s.%s", record.Namespace, record.Name) + } else { + return record.Name + } +} diff --git a/schema_test.go b/schema_test.go new file mode 100644 index 0000000..fa55772 --- /dev/null +++ b/schema_test.go @@ -0,0 +1,885 @@ +package dkafka + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" + + "github.com/eoscanada/eos-go" +) + +func Test_resolveFieldTypeSchema(t *testing.T) { + initBuiltInTypesForTables() + type args struct { + fieldType string + abi *ABI + } + type test struct { + name string + args args + want Schema + wantErr bool + } + + newTest := func(eosType string, want string, wantErr bool) test { + return test{ + name: fmt.Sprintf("%s->%s", eosType, want), + args: args{eosType, nil}, + want: TypedSchema{Type: want, EosType: eosType}, + wantErr: wantErr, + } + } + + tests := []test{ + newTest( + "bool", + "boolean", + false), + newTest( + "int8", + "int", + false, + ), + newTest( + "uint8", + "int", + false, + ), + newTest( + "int16", + "int", + false, + ), + newTest( + "uint16", + "int", + false, + ), + newTest( + "int32", + "int", + false, + ), + { + name: "int32[]->[]int", + args: args{"int32[]", nil}, + want: NewArray(TypedSchema{Type: "int", EosType: "int32"}), + wantErr: false, + }, + { + name: "int32$->['null', 'int']", + args: args{"int32$", nil}, + want: NewOptional(TypedSchema{Type: "int", EosType: "int32"}), + wantErr: false, + }, + { + name: "int32?$->['null','int']", + args: args{"int32?$", nil}, + want: NewOptional(TypedSchema{Type: "int", EosType: "int32"}), + wantErr: false, + }, + { + name: "int32[]?->['null',[]int]", + args: args{"int32[]?$", nil}, + want: NewOptional(NewArray(TypedSchema{Type: "int", EosType: "int32"})), + wantErr: false, + }, + { + name: "int32?->['null','int']", + args: args{"int32?", nil}, + want: NewOptional(TypedSchema{Type: "int", EosType: "int32"}), + wantErr: false, + }, + newTest( + "uint32", + "long", + false, + ), + newTest( + "int64", + "long", + false, + ), + { + name: "uint64->long", + args: args{"uint64", nil}, + want: TypedSchema{Type: "long", EosType: "uint64", LogicalType: "eos.uint64"}, + wantErr: false, + }, + newTest( + "varint32", + "int", + false, + ), + newTest( + "varuint32", + "long", + false, + ), + newTest( + "float32", + "float", + false, + ), + newTest( + "float64", + "double", + false, + ), + newTest( + "name", + "string", + false, + ), + newTest( + "bytes", + "bytes", + false, + ), + newTest( + "string", + "string", + false, + ), + newTest( + "checksum160", + "bytes", + false, + ), + newTest( + "checksum256", + "bytes", + false, + ), + newTest( + "checksum512", + "bytes", + false, + ), + newTest( + "symbol_code", + "string", + false, + ), + { + name: "unknown->error", + args: args{"unknown", &ABI{ABI: &eos.ABI{}}}, + want: nil, + wantErr: true, + }, + { + name: "struct->record", + args: args{"my_struct", &ABI{ABI: &eos.ABI{ + Types: []eos.ABIType{{ + NewTypeName: "int64_alias", + Type: "int64", + }}, + Structs: []eos.StructDef{{ + Name: "my_struct", + Base: "", + Fields: []eos.FieldDef{ + { + Name: "fieldA", + Type: "uint32", + }, + { + Name: "fieldB", + Type: "int64_alias", + }, + }, + }}, + }}}, + want: RecordSchema{ + Type: "record", + Name: "MyStruct", + Fields: []FieldSchema{ + { + Name: "fieldA", + Type: TypedSchema{Type: "long", EosType: "uint32"}, + }, + { + Name: "fieldB", + Type: TypedSchema{Type: "long", EosType: "int64"}, + }, + }, + }, + wantErr: false, + }, + { + name: "struct-inheritance", + args: args{"my_struct", &ABI{ABI: &eos.ABI{ + Types: []eos.ABIType{{ + NewTypeName: "int64_alias", + Type: "int64", + }}, + Structs: []eos.StructDef{ + { + Name: "parent", + Base: "", + Fields: []eos.FieldDef{ + { + Name: "parentFieldA", + Type: "string", + }, + { + Name: "parentFieldB", + Type: "int32", + }, + }, + }, + { + Name: "my_struct", + Base: "parent", + Fields: []eos.FieldDef{ + { + Name: "fieldA", + Type: "uint32", + }, + { + Name: "fieldB", + Type: "int64_alias", + }, + }, + }}, + }}}, + want: RecordSchema{ + Type: "record", + Name: "MyStruct", + Fields: []FieldSchema{ + { + Name: "parentFieldA", + Type: TypedSchema{Type: "string", EosType: "string"}, + }, + { + Name: "parentFieldB", + Type: TypedSchema{Type: "int", EosType: "int32"}, + }, + { + Name: "fieldA", + Type: TypedSchema{Type: "long", EosType: "uint32"}, + }, + { + Name: "fieldB", + Type: TypedSchema{Type: "long", EosType: "int64"}, + }, + }, + }, + wantErr: false, + }, + { + name: "union-multiple-types-nullable", + args: args{"nullable_union_record", &ABI{ABI: &eos.ABI{ + Types: []eos.ABIType{{ + NewTypeName: "nullable_union", + Type: "variant_nullable_union", + }}, + Structs: []eos.StructDef{{ + Name: "nullable_union_record", + Base: "", + Fields: []eos.FieldDef{ + { + Name: "nullable_union_field", + Type: "nullable_union?", + }, + }, + }, + }, + Variants: []eos.VariantDef{{ + Name: "variant_nullable_union", + Types: []string{"string", "int8"}, + }}, + }}}, + want: RecordSchema{ + Type: "record", + Name: "NullableUnionRecord", + Fields: []FieldSchema{ + { + Name: "nullable_union_field", + Type: []interface{}{"null", TypedSchema{Type: "string", EosType: "string"}, TypedSchema{Type: "int", EosType: "int8"}}, + Default: _defaultNull, + }, + }, + }, + wantErr: false, + }, + { + name: "uber-variant", + args: args{"uber_variant", &ABI{ABI: &eos.ABI{ + Types: []eos.ABIType{ + { + NewTypeName: "INT8_VEC", + Type: "int8[]", + }, + { + NewTypeName: "INT16_VEC", + Type: "int16[]", + }, + { + NewTypeName: "INT32_VEC", + Type: "int32[]", + }, + { + NewTypeName: "INT64_VEC", + Type: "int64[]", + }, + { + NewTypeName: "UINT8_VEC", + Type: "uint8[]", + }, + { + NewTypeName: "UINT16_VEC", + Type: "uint16[]", + }, + { + NewTypeName: "UINT32_VEC", + Type: "uint32[]", + }, + { + NewTypeName: "UINT64_VEC", + Type: "uint64[]", + }, + { + NewTypeName: "FLOAT32_VEC", + Type: "float32[]", + }, + { + NewTypeName: "FLOAT64_VEC", + Type: "float64[]", + }, + { + NewTypeName: "STRING_VEC", + Type: "string[]", + }, + { + NewTypeName: "BOOL_VEC", + Type: "boolean[]", + }, + { + NewTypeName: "key_value_store", + Type: HardcodedUberVariant, + }, + }, + Structs: []eos.StructDef{{ + Name: "uber_variant", + Base: "", + Fields: []eos.FieldDef{ + { + Name: "default_value", + Type: HardcodedUberVariant, + }, + }, + }, + }, + Variants: []eos.VariantDef{{ + Name: HardcodedUberVariant, + Types: []string{"int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float32", "float64", "string", "boolean", "INT8_VEC", "INT16_VEC", "INT32_VEC", "INT64_VEC", "UINT8_VEC", "UINT16_VEC", "UINT32_VEC", "UINT64_VEC", "FLOAT32_VEC", "FLOAT64_VEC", "STRING_VEC", "BOOL_VEC"}, + }}, + }}}, + want: RecordSchema{ + Type: "record", + Name: "UberVariant", + Fields: []FieldSchema{ + { + Name: "default_value", + Type: hardcodedVariantType[HardcodedUberVariant], + }, + }, + }, + wantErr: false, + }, + { + name: "union-multiple-types-nullable", + args: args{"nullable_union_record", &ABI{ABI: &eos.ABI{ + Types: []eos.ABIType{{ + NewTypeName: "nullable_union", + Type: "variant_nullable_union", + }}, + Structs: []eos.StructDef{{ + Name: "nullable_union_record", + Base: "", + Fields: []eos.FieldDef{ + { + Name: "nullable_union_field", + Type: "nullable_union?", + }, + }, + }, + }, + Variants: []eos.VariantDef{{ + Name: "variant_nullable_union", + Types: []string{"string", "int8"}, + }}, + }}}, + want: RecordSchema{ + Type: "record", + Name: "NullableUnionRecord", + Fields: []FieldSchema{ + { + Name: "nullable_union_field", + Type: []interface{}{"null", TypedSchema{Type: "string", EosType: "string"}, TypedSchema{Type: "int", EosType: "int8"}}, + Default: _defaultNull, + }, + }, + }, + wantErr: false, + }, + { + name: "mix-extended-assets-and-assets", + args: args{"my_struct", &ABI{ABI: &eos.ABI{ + Structs: []eos.StructDef{{ + Name: "my_struct", + Base: "", + Fields: []eos.FieldDef{ + { + Name: "fieldA", + Type: "extended_asset", + }, + { + Name: "fieldB", + Type: "asset", + }, + }, + }}, + }}}, + want: RecordSchema{ + Type: "record", + Name: "MyStruct", + Fields: []FieldSchema{ + { + Name: "fieldA", + Type: RecordSchema{ + Type: "record", + Name: "ExtendedAsset", + Namespace: "eosio", + Convert: "eosio.ExtendedAsset", + Fields: []FieldSchema{ + { + Name: "quantity", + Type: RecordSchema{ + Type: "record", + Name: "Asset", + Namespace: "eosio", + Convert: "eosio.Asset", + Fields: []FieldSchema{ + { + Name: "amount", + Type: json.RawMessage(`{ + "type": "bytes", + "logicalType": "decimal", + "precision": 32, + "scale": 8 + }`), + }, + { + Name: "symbol", + Type: "string", + }, + { + Name: "precision", + Type: "int", + }, + }, + }, + }, + { + Name: "contract", + Type: "string", + }, + }, + }, + }, + { + Name: "fieldB", + Type: "eosio.Asset", + }, + }, + }, + wantErr: false, + }, + { + name: "mix-asset-and-extended-assets", + args: args{"my_struct", &ABI{ABI: &eos.ABI{ + Structs: []eos.StructDef{{ + Name: "my_struct", + Base: "", + Fields: []eos.FieldDef{ + { + Name: "fieldA", + Type: "asset", + }, + { + Name: "fieldB", + Type: "extended_asset", + }, + }, + }}, + }}}, + want: RecordSchema{ + Type: "record", + Name: "MyStruct", + Fields: []FieldSchema{ + { + Name: "fieldA", + Type: RecordSchema{ + Type: "record", + Name: "Asset", + Namespace: "eosio", + Convert: "eosio.Asset", + Fields: []FieldSchema{ + { + Name: "amount", + Type: json.RawMessage(`{ + "type": "bytes", + "logicalType": "decimal", + "precision": 32, + "scale": 8 + }`), + }, + { + Name: "symbol", + Type: "string", + }, + { + Name: "precision", + Type: "int", + }, + }, + }, + }, + { + Name: "fieldB", + Type: RecordSchema{ + Type: "record", + Name: "ExtendedAsset", + Namespace: "eosio", + Convert: "eosio.ExtendedAsset", + Fields: []FieldSchema{ + { + Name: "quantity", + Type: "eosio.Asset", + }, + { + Name: "contract", + Type: "string", + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := resolveFieldTypeSchema(tt.args.abi, tt.args.fieldType, make(map[string]string)) + if (err != nil) != tt.wantErr { + t.Errorf("resolveFieldTypeSchema() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("resolveFieldTypeSchema() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_variantResolveFieldTypeSchema(t *testing.T) { + initBuiltInTypesForTables() + type args struct { + fieldType string + abi *ABI + } + tests := []struct { + name string + args args + want Schema + wantErr bool + }{ + { + name: "variant->union", + args: args{"regproducer2", &ABI{ABI: &eos.ABI{ + Types: []eos.ABIType{{ + NewTypeName: "block_signing_authority", + Type: "variant_block_signing_authority_v0", + }}, + Structs: []eos.StructDef{{ + Name: "block_signing_authority_v0", + Base: "", + Fields: []eos.FieldDef{ + { + Name: "threshold", + Type: "uint32", + }, + }, + }, { + Name: "regproducer2", + Base: "", + Fields: []eos.FieldDef{ + { + Name: "producer_authority", + Type: "block_signing_authority", + }, + }, + }, + }, + Variants: []eos.VariantDef{{ + Name: "variant_block_signing_authority_v0", + Types: []string{"block_signing_authority_v0"}, + }}, + }}}, + want: RecordSchema{ + Type: "record", + Name: "Regproducer2", + Fields: []FieldSchema{ + { + Name: "producer_authority", + Type: RecordSchema{ + Type: "record", + Name: "BlockSigningAuthorityV0", + Fields: []FieldSchema{ + { + Name: "threshold", + Type: TypedSchema{Type: "long", EosType: "uint32"}, + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "variant->union multiple types", + args: args{"pair_string_key_value_store", &ABI{ABI: &eos.ABI{ + Types: []eos.ABIType{ + { + NewTypeName: "key_value_store", + Type: "variant_int8_string", + }, + }, + Structs: []eos.StructDef{{ + Name: "pair_string_key_value_store", + Base: "", + Fields: []eos.FieldDef{ + { + Name: "first", + Type: "string", + }, + { + Name: "second", + Type: "key_value_store", + }, + }, + }, + }, + Variants: []eos.VariantDef{{ + Name: "variant_int8_string", + Types: []string{"int8", "string"}, + }}, + }}}, + want: RecordSchema{ + Type: "record", + Name: "PairStringKeyValueStore", + Fields: []FieldSchema{ + { + Type: TypedSchema{Type: "string", EosType: "string"}, + Name: "first", + }, + { + Type: []interface{}{ + TypedSchema{ + EosType: "int8", + Type: "int", + }, + TypedSchema{ + EosType: "string", + Type: "string", + }, + }, + Name: "second", + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := resolveFieldTypeSchema(tt.args.abi, tt.args.fieldType, make(map[string]string)) + if (err != nil) != tt.wantErr { + t.Errorf("resolveFieldTypeSchema() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("resolveFieldTypeSchema() = %v, want %v", got, tt.want) + } + }) + } +} + +var actionABI eos.ABI = eos.ABI{ + Types: []eos.ABIType{{ + NewTypeName: "int64_alias", + Type: "int64", + }}, + Structs: []eos.StructDef{{ + Name: "my_action", + Base: "", + Fields: []eos.FieldDef{ + { + Name: "fieldA", + Type: "uint32", + }, + { + Name: "fieldB", + Type: "int64_alias", + }, + }, + }}, + Actions: []eos.ActionDef{{ + Name: "my_action", + Type: "my_action", + }}, +} + +func TestActionToRecord(t *testing.T) { + type args struct { + abi *ABI + name eos.ActionName + } + tests := []struct { + name string + args args + want RecordSchema + wantErr bool + }{ + { + "known_action", + args{ + &ABI{ABI: &actionABI}, + "my_action", + }, + RecordSchema{ + Type: "record", + Name: "MyAction", + Fields: []FieldSchema{ + { + Name: "fieldA", + Type: TypedSchema{Type: "long", EosType: "uint32"}, + }, + { + Name: "fieldB", + Type: TypedSchema{Type: "long", EosType: "int64"}, + }, + }, + }, + false, + }, + { + "unknown_action", + args{ + &ABI{ABI: &actionABI}, + "unknown_action", + }, + RecordSchema{}, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ActionToRecord(tt.args.abi, tt.args.name) + if (err != nil) != tt.wantErr { + t.Errorf("ActionToRecord() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ActionToRecord() = %v, want %v", got, tt.want) + } + }) + } +} + +var tableABI eos.ABI = eos.ABI{ + Types: []eos.ABIType{{ + NewTypeName: "int64_alias", + Type: "int64", + }}, + Structs: []eos.StructDef{{ + Name: "my_table_struct", + Base: "", + Fields: []eos.FieldDef{ + { + Name: "fieldA", + Type: "uint32", + }, + { + Name: "fieldB", + Type: "int64_alias", + }, + }, + }}, + Tables: []eos.TableDef{{ + Name: "my.table", + IndexType: "i64", + Type: "my_table_struct", + }}, +} + +func TestTableToRecord(t *testing.T) { + type args struct { + abi *ABI + name eos.TableName + } + tests := []struct { + name string + args args + want RecordSchema + wantErr bool + }{ + { + "known table", + args{ + &ABI{ABI: &tableABI}, + "my.table", + }, + RecordSchema{ + Type: "record", + Name: "MyTableStruct", + Fields: []FieldSchema{ + { + Name: "fieldA", + Type: TypedSchema{Type: "long", EosType: "uint32"}, + }, + { + Name: "fieldB", + Type: TypedSchema{Type: "long", EosType: "int64"}, + }, + }, + }, + false, + }, + { + "unknown table", + args{ + &ABI{ABI: &actionABI}, + "unknown.table", + }, + RecordSchema{}, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := TableToRecord(tt.args.abi, tt.args.name) + if (err != nil) != tt.wantErr { + t.Errorf("TableToRecord() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("TableToRecord() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/schemaregistry.go b/schemaregistry.go new file mode 100644 index 0000000..b8d2617 --- /dev/null +++ b/schemaregistry.go @@ -0,0 +1,123 @@ +package dkafka + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "regexp" +) + +const ( + contentType = "application/vnd.schemaregistry.v1+json" + AvroType = "AVRO" + subjectVersions = "/subjects/%s/versions" +) + +var cleanupSchemaRegex *regexp.Regexp + +func init() { + cleanupSchemaRegex = regexp.MustCompile(`\r?\n`) +} + +type SchemaRegistry interface { + RegisterSchema(subject string, schema string) (id int, err error) +} + +type HttpSchemaRegistry struct { + schemaRegistryURL string + credentials *srCredentials + httpClient *http.Client +} + +type srCredentials struct { + username string + password string +} + +type schemaRequest struct { + Schema string `json:"schema"` + SchemaType string `json:"schemaType"` +} + +type schemaResponse struct { + Subject string `json:"subject"` + Version int `json:"version"` + Schema string `json:"schema"` + // SchemaType *SchemaType `json:"schemaType"` + ID int `json:"id"` + // References []Reference `json:"references"` +} + +func (client *HttpSchemaRegistry) RegisterSchema(subject string, schema string) (int, error) { + schema = cleanupSchemaRegex.ReplaceAllString(schema, " ") + schemaReq := schemaRequest{Schema: schema, SchemaType: AvroType} + schemaBytes, err := json.Marshal(schemaReq) + if err != nil { + return 0, err + } + payload := bytes.NewBuffer(schemaBytes) + resp, err := client.httpRequest("POST", fmt.Sprintf(subjectVersions, url.QueryEscape(subject)), payload) + if err != nil { + return 0, err + } + schemaResp := new(schemaResponse) + err = json.Unmarshal(resp, &schemaResp) + if err != nil { + return 0, err + } + return schemaResp.ID, nil +} + +func (client *HttpSchemaRegistry) httpRequest(method, uri string, payload io.Reader) ([]byte, error) { + + url := fmt.Sprintf("%s%s", client.schemaRegistryURL, uri) + req, err := http.NewRequest(method, url, payload) + if err != nil { + return nil, err + } + if client.credentials != nil { + req.SetBasicAuth(client.credentials.username, client.credentials.password) + } + req.Header.Set("Content-Type", contentType) + + resp, err := client.httpClient.Do(req) + if err != nil { + return nil, err + } + + if resp != nil { + defer resp.Body.Close() + } + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return nil, createError(resp) + } + + return ioutil.ReadAll(resp.Body) +} + +type MockSchemaRegistry struct{} + +type Error struct { + Code int `json:"error_code"` + Message string `json:"message"` + str *bytes.Buffer +} + +func (e Error) Error() string { + return e.str.String() +} + +func createError(resp *http.Response) error { + err := Error{str: bytes.NewBuffer(make([]byte, 0, resp.ContentLength))} + decoder := json.NewDecoder(io.TeeReader(resp.Body, err.str)) + marshalErr := decoder.Decode(&err) + if marshalErr != nil { + return fmt.Errorf("%s", resp.Status) + } + + return err +} diff --git a/sender.go b/sender.go index 262b493..a8b6c7d 100644 --- a/sender.go +++ b/sender.go @@ -4,101 +4,169 @@ import ( "context" "encoding/json" "fmt" - "sync" "time" "github.com/confluentinc/confluent-kafka-go/kafka" + "github.com/streamingfast/bstream/forkable" "go.uber.org/zap" ) -type sender interface { - Send(msg *kafka.Message) error - CommitIfAfter(ctx context.Context, cursor string, minimumDelay time.Duration) error - Commit(ctx context.Context, cursor string) error -} +const CursorHeaderKey = "dkafka_cursor" +const PreviousCursorHeaderKey = "dkafka_prev_cursor" -type kafkaSender struct { - sync.RWMutex - lastCommit time.Time - trxStarted bool - producer *kafka.Producer - cp checkpointer - useTransactions bool +type location interface { + blockId() string + blockNum() uint32 + opaqueCursor() string + time() time.Time + timeHeader() kafka.Header + previousOpaqueCursor() string } -func (s *kafkaSender) Send(msg *kafka.Message) error { - s.RLock() - defer s.RUnlock() - return s.producer.Produce(msg, nil) +type Sender interface { + Send(ctx context.Context, messages []*kafka.Message, location location) error + SaveCP(ctx context.Context, location location) error } -func (s *kafkaSender) Close(ctx context.Context) { - if s.useTransactions { - if err := s.producer.CommitTransaction(ctx); err != nil { - zlog.Error("cannot commit transaction on close", zap.Error(err)) +type DryRunSender struct{} + +func (s *DryRunSender) Send(ctx context.Context, messages []*kafka.Message, location location) error { + for i, msg := range messages { + outJson, err := messageToJSON(msg) + if err != nil { + return err } + fmt.Printf("%d: %s", i, string(outJson)) } - s.producer.Close() + return nil } -func (s *kafkaSender) CommitIfAfter(ctx context.Context, cursor string, minimumDelay time.Duration) error { - if time.Since(s.lastCommit) > minimumDelay { - zlog.Debug("commiting cursor") - return s.Commit(ctx, cursor) - } +func (s *DryRunSender) SaveCP(ctx context.Context, location location) error { return nil } -func (s *kafkaSender) Commit(ctx context.Context, cursor string) error { - s.Lock() // full write lock - defer s.Unlock() - - if err := s.cp.Save(cursor); err != nil { - return fmt.Errorf("saving cursor: %w", err) - } - s.lastCommit = time.Now() - - if s.useTransactions { - if err := s.producer.CommitTransaction(ctx); err != nil { - return fmt.Errorf("committing transaction: %w", err) - } +type FastKafkaSender struct { + producer *kafka.Producer + headers []kafka.Header + topic string + abiCodec ABICodec +} - if err := s.producer.BeginTransaction(); err != nil { - return fmt.Errorf("beginning transaction: %w", err) +func (s *FastKafkaSender) Send(ctx context.Context, messages []*kafka.Message, location location) error { + zlog.Debug("send messages", zap.Uint32("block_id", location.blockNum()), zap.String("block_id", location.blockId()), zap.Int("nb", len(messages))) + for _, msg := range messages { + msg.Headers = appendLocation(msg.Headers, location) + if err := send(s.producer, msg); err != nil { + return err } } + zlog.Debug("messages sent", zap.Uint32("block_id", location.blockNum()), zap.String("block_id", location.blockId()), zap.Int("nb", len(messages))) return nil } -func getKafkaProducer(conf kafka.ConfigMap, name string) (*kafka.Producer, error) { - producerConfig := cloneConfig(conf) - if name != "" { - producerConfig["transactional.id"] = name +func (s *FastKafkaSender) SaveCP(ctx context.Context, location location) error { + cursor := location.opaqueCursor() + c, err := forkable.CursorFromOpaque(cursor) + if err != nil { + zlog.Error("FastKafkaSender.SaveCP() cannot decode cursor", zap.String("cursor", cursor), zap.Error(err)) + return err } - return kafka.NewProducer(&producerConfig) + zlog.Debug("save checkpoint", + zap.String("cursor", cursor), + zap.Stringer("plain_cursor", c), + zap.Stringer("cursor_block", c.Block), + zap.Stringer("cursor_head_block", c.HeadBlock), + zap.Stringer("cursor_LIB", c.LIB), + ) + checkpoint := newCheckpointMap(c, location.time()) + codec, err := s.abiCodec.GetCodec(CheckpointSchema.AsCodecId(), 0) + if err != nil { + return fmt.Errorf("SaveCP() fail to get codec for %s: %w", dkafkaCheckpoint, err) + } + value, err := codec.Marshal(nil, checkpoint) + if err != nil { + return fmt.Errorf("SaveCP() fail to marshal %s: %w", dkafkaCheckpoint, err) + } + ce_id := hashString(cursor) + headers := append(s.headers, + // add codec specific content type + codec.GetHeaders()..., + ) + headers = append(headers, + kafka.Header{ + Key: "ce_id", + Value: ce_id, + }, + kafka.Header{ + Key: "ce_type", + Value: []byte(dkafkaCheckpoint), + }, + location.timeHeader(), + newCursorHeader(cursor), + newPreviousCursorHeader(location.previousOpaqueCursor()), + ) + + msg := kafka.Message{ + Key: nil, + Headers: headers, + Value: value, + TopicPartition: kafka.TopicPartition{ + Topic: &s.topic, + Partition: kafka.PartitionAny, + }, + } + + return send(s.producer, &msg) } -func getKafkaSender(producer *kafka.Producer, cp checkpointer, useTransactions bool) (*kafkaSender, error) { - if useTransactions { - ctx := context.Background() //FIXME - if err := producer.InitTransactions(ctx); err != nil { - return nil, fmt.Errorf("running InitTransactions: %w", err) - } +func appendLocation(headers []kafka.Header, location location) []kafka.Header { + return append(headers, newCursorHeader(location.opaqueCursor()), + newPreviousCursorHeader(location.previousOpaqueCursor())) +} - // initial transaction - if err := producer.BeginTransaction(); err != nil { - return nil, fmt.Errorf("running BeginTransaction: %w", err) +func newCursorHeader(cursor string) kafka.Header { + return kafka.Header{ + Key: CursorHeaderKey, + Value: []byte(cursor), + } +} + +func newPreviousCursorHeader(cursor string) kafka.Header { + return kafka.Header{ + Key: PreviousCursorHeaderKey, + Value: []byte(cursor), + } +} + +func send(producer *kafka.Producer, msg *kafka.Message) error { + if err := producer.Produce(msg, nil); err != nil { + if err.(kafka.Error).Code() == kafka.ErrQueueFull { + // Producer queue is full, wait 1s for messages + // to be delivered then try again. + zlog.Info("kafka producer message queue full wait 1 second and retry") + time.Sleep(time.Second) + // retry recursively => succeed to send or crash + return send(producer, msg) } + return fmt.Errorf("sender fail to produce message: %w", err) } + return nil +} - return &kafkaSender{ - cp: cp, - producer: producer, - useTransactions: useTransactions, - }, nil +func NewFastSender(ctx context.Context, producer *kafka.Producer, topic string, headers []kafka.Header, abiCodec ABICodec) Sender { + ks := FastKafkaSender{ + producer: producer, + headers: headers, + topic: topic, + abiCodec: abiCodec, + } + return &ks } -type dryRunSender struct{} +func getKafkaProducer(conf kafka.ConfigMap) (*kafka.Producer, error) { + producerConfig := cloneConfig(conf) + return kafka.NewProducer(&producerConfig) +} type fakeMessage struct { Topic string `json:"topic"` @@ -110,7 +178,7 @@ type fakeMessage struct { Payload json.RawMessage `json:"payload"` } -func (s *dryRunSender) Send(msg *kafka.Message) error { +func messageToJSON(msg *kafka.Message) (json.RawMessage, error) { out := &fakeMessage{ Payload: json.RawMessage(msg.Value), Key: string(msg.Key), @@ -118,18 +186,5 @@ func (s *dryRunSender) Send(msg *kafka.Message) error { for _, h := range msg.Headers { out.Headers = append(out.Headers, h.Key, string(h.Value)) } - outjson, err := json.Marshal(out) - if err != nil { - return err - } - fmt.Println(string(outjson)) - return nil -} - -func (s *dryRunSender) CommitIfAfter(context.Context, string, time.Duration) error { - return nil -} - -func (s *dryRunSender) Commit(context.Context, string) error { - return nil + return json.Marshal(out) } diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..15db311 --- /dev/null +++ b/shell.nix @@ -0,0 +1,15 @@ +{ pkgs ? import { } }: +with pkgs; + + +mkShell { + hardeningDisable = [ "all" ]; # https://github.com/go-delve/delve/issues/3085 + buildInputs = [ + pkgs.go + + # Use to test avro to POJO generation + pkgs.wget + pkgs.temurin-bin + pkgs.docker-compose + ]; +} \ No newline at end of file diff --git a/slice.go b/slice.go new file mode 100644 index 0000000..fd46052 --- /dev/null +++ b/slice.go @@ -0,0 +1,37 @@ +package dkafka + +type IndexedEntry[T any] struct { + Index int + Entry T +} + +func NewIndexedEntrySlice[T any](slice []T) []*IndexedEntry[T] { + indexedEntrySlice := make([]*IndexedEntry[T], len(slice)) + for i, entry := range slice { + e := new(IndexedEntry[T]) + e.Entry = entry + e.Index = i + indexedEntrySlice[i] = e + } + return indexedEntrySlice +} + +func Reverse[T any](input []T) []T { + inputLen := len(input) + output := make([]T, inputLen) + + for i, n := range input { + j := inputLen - i - 1 + + output[j] = n + } + + return output +} + +func ReverseInPlace[T any](input []T) []T { + for i, j := 0, len(input)-1; i < j; i, j = i+1, j-1 { + input[i], input[j] = input[j], input[i] + } + return input +} diff --git a/slice_test.go b/slice_test.go new file mode 100644 index 0000000..f79e5b1 --- /dev/null +++ b/slice_test.go @@ -0,0 +1,99 @@ +package dkafka + +import ( + "reflect" + "testing" +) + +func TestReverse(t *testing.T) { + tests := []struct { + name string + args []int + want []int + }{ + { + name: "int slice reverse order", + args: []int{1, 2, 3, 4, 5}, + want: []int{5, 4, 3, 2, 1}, + }, + { + name: "int empty slice reverse order", + args: []int{}, + want: []int{}, + }, + { + name: "int singleton slice reverse order", + args: []int{1}, + want: []int{1}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Reverse(tt.args); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Reverse() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestReverseInPlace(t *testing.T) { + tests := []struct { + name string + args []int + want []int + }{ + { + name: "int slice reverse order", + args: []int{1, 2, 3, 4, 5}, + want: []int{5, 4, 3, 2, 1}, + }, + { + name: "int empty slice reverse order", + args: []int{}, + want: []int{}, + }, + { + name: "int singleton slice reverse order", + args: []int{1}, + want: []int{1}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ReverseInPlace(tt.args); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Reverse() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNewIndexedEntrySlice(t *testing.T) { + tests := []struct { + name string + args []int + want []*IndexedEntry[int] + }{ + { + name: "index slice many", + args: []int{1, 2, 3, 4, 5}, + want: []*IndexedEntry[int]{{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 5}}, + }, + { + name: "index singleton slice", + args: []int{1}, + want: []*IndexedEntry[int]{{0, 1}}, + }, + { + name: "index empty slice", + args: []int{}, + want: []*IndexedEntry[int]{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewIndexedEntrySlice(tt.args); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewIndexedEntrySlice() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/streamedabicodec.go b/streamedabicodec.go new file mode 100644 index 0000000..74568c4 --- /dev/null +++ b/streamedabicodec.go @@ -0,0 +1,384 @@ +package dkafka + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + pbabicodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/abicodec/v1" + pbcodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/codec/v1" + "github.com/eoscanada/eos-go" + "github.com/linkedin/goavro/v2" + "github.com/riferrei/srclient" + pbbstream "github.com/streamingfast/pbgo/dfuse/bstream/v1" + "go.uber.org/zap" +) + +const ( + // CodecIdPrefix is the prefix used for codec IDs in the StreamedAbiCodec. + DefaultCompatibility = srclient.Forward +) + +type AbiRepository interface { + GetAbi(contract string, blockNum uint32) (*ABI, error) + IsNOOP() bool +} + +type DfuseAbiRepository struct { + overrides map[string]*ABI + abiCodecCli pbabicodec.DecoderClient + context context.Context +} + +func (a *DfuseAbiRepository) GetAbi(contract string, blockNum uint32) (*ABI, error) { + if a.overrides != nil { + if abi, ok := a.overrides[contract]; ok { + return abi, nil + } + } + if a.abiCodecCli == nil { + return nil, fmt.Errorf("unable to get abi for contract %q, no client, no overrides you need at least one of them", contract) + } + resp, err := a.abiCodecCli.GetAbi(a.context, &pbabicodec.GetAbiRequest{ + Account: contract, + AtBlockNum: blockNum, + }) + if err != nil { + return nil, fmt.Errorf("fail to call dfuse abi server for contract %q: %w", contract, err) + } + + var eosAbi *eos.ABI + err = json.Unmarshal([]byte(resp.JsonPayload), &eosAbi) + if err != nil { + return nil, fmt.Errorf("unable to decode abi for contract %q: %w", contract, err) + } + var abi = ABI{eosAbi, resp.AbiBlockNum, contract, true} + zlog.Info("new ABI loaded", zap.String("contract", contract), zap.Uint32("block_num", blockNum), zap.Uint32("abi_block_num", abi.AbiBlockNum)) + return &abi, nil +} + +func (a *DfuseAbiRepository) IsNOOP() bool { + return a.overrides == nil && a.abiCodecCli == nil +} + +func (a CodecId) String() string { + return fmt.Sprintf("%v::%v", a.Account, a.Name) +} + +type StreamedAbiCodec struct { + bootstrapper AbiRepository + latestABIs map[string]*ABI + abiHistories map[string][]*ABI + getSchema MessageSchemaSupplier + schemaRegistryClient srclient.ISchemaRegistryClient + account string + codecCache map[CodecId]Codec + schemaRegistryURL string + staticSchemas []MessageSchema + compatibility srclient.CompatibilityLevel +} + +type StreamAbiCodecConstructor = func(AbiRepository, + MessageSchemaSupplier, + srclient.ISchemaRegistryClient, + string, + string, + srclient.CompatibilityLevel, +) ABICodec + +func NewStreamedAbiCodec( + bootstrapper AbiRepository, + getSchema MessageSchemaSupplier, + schemaRegistryClient srclient.ISchemaRegistryClient, + account string, + schemaRegistryURL string, + compatibility srclient.CompatibilityLevel, +) ABICodec { + return newStreamedAbiCodec( + bootstrapper, + getSchema, + schemaRegistryClient, + account, + schemaRegistryURL, + []MessageSchema{CheckpointMessageSchema}, + compatibility, + ) +} + +func NewStreamedAbiCodecWithTransaction( + bootstrapper AbiRepository, + getSchema MessageSchemaSupplier, + schemaRegistryClient srclient.ISchemaRegistryClient, + account string, + schemaRegistryURL string, + compatibility srclient.CompatibilityLevel, +) ABICodec { + return newStreamedAbiCodec( + bootstrapper, + getSchema, + schemaRegistryClient, + account, + schemaRegistryURL, + []MessageSchema{CheckpointMessageSchema, TransactionMessageSchema}, + compatibility, + ) +} + +func newStreamedAbiCodec( + bootstrapper AbiRepository, + getSchema MessageSchemaSupplier, + schemaRegistryClient srclient.ISchemaRegistryClient, + account string, + schemaRegistryURL string, + staticSchemas []MessageSchema, + compatibility srclient.CompatibilityLevel, +) ABICodec { + codec := &StreamedAbiCodec{ + bootstrapper: bootstrapper, + getSchema: getSchema, + schemaRegistryClient: schemaRegistryClient, + account: account, + schemaRegistryURL: schemaRegistryURL, + staticSchemas: staticSchemas, + latestABIs: make(map[string]*ABI), + abiHistories: make(map[string][]*ABI), + codecCache: make(map[CodecId]Codec), + compatibility: compatibility, + } + codec.resetCodecs() + return codec +} + +func (s *StreamedAbiCodec) IsNOOP() bool { + return s.bootstrapper.IsNOOP() +} + +func (s *StreamedAbiCodec) GetCodec(codecId CodecId, blockNum uint32) (Codec, error) { + + if codec, found := s.codecCache[codecId]; found { + return codec, nil + } + abi, err := s.getLatestAbi(codecId.Account, blockNum) + if err != nil { + return nil, fmt.Errorf("cannot get ABI for codec: %s, error: %w", codecId, err) + } + zlog.Debug("create schema from abi", zap.Uint32("block_num", blockNum), zap.Uint32("abi_block_num", abi.AbiBlockNum), zap.Stringer("entry", codecId)) + messageSchema, err := s.getSchema(codecId.Name, abi) + if err != nil { + return nil, fmt.Errorf("fail to generate schema: %s, from ABI at block: %d, abi_block: %d, error: %w", codecId, blockNum, abi.AbiBlockNum, err) + } + codec, err := s.newCodec(messageSchema) + if err != nil { + return nil, fmt.Errorf("StreamedAbiCodec.newCodec fail to create codec for schema %s, error: %w", messageSchema.Name, err) + } + zlog.Debug("register codec into cache", zap.Stringer("name", codecId)) + s.codecCache[codecId] = codec + return codec, nil +} + +func (s *StreamedAbiCodec) getLatestAbi(account string, blockNum uint32) (latestAbi *ABI, err error) { + var found = false + if latestAbi, found = s.latestABIs[account]; !found { + if bootstrapAbi, er := s.bootstrapper.GetAbi(account, blockNum); er != nil { + err = fmt.Errorf("fail to bootstrap ABI at block: %d, error: %w", blockNum, err) + } else { + latestAbi = bootstrapAbi + s.latestABIs[account] = latestAbi + } + } + return +} + +func (s *StreamedAbiCodec) DecodeDBOp(in *pbcodec.DBOp, blockNum uint32) (decoded *decodedDBOp, err error) { + decoded = &decodedDBOp{DBOp: in} + err = s.decodeDBOp(decoded, blockNum) + return +} + +func (s *StreamedAbiCodec) decodeDBOp(op *decodedDBOp, blockNum uint32) error { + latestAbi, err := s.getLatestAbi(op.Code, blockNum) + if err != nil { + return fmt.Errorf("fail to get ABI for decoding dbop in block: %d, error: %w", blockNum, err) + } + abi := latestAbi + tableDef := abi.TableForName(eos.TableName(op.TableName)) + if tableDef == nil { + return fmt.Errorf("table %s not present in ABI for contract %s at block: %d", op.TableName, op.Code, blockNum) + } + + if len(op.NewData) > 0 { + asMap, err := abi.DecodeTableRowTypedNative(tableDef.Type, op.NewData) + if err != nil { + return fmt.Errorf("fail to decode new row at block: %d, error: %w", blockNum, err) + } + op.NewJSON = asMap + } + if len(op.OldData) > 0 { + asMap, err := abi.DecodeTableRowTypedNative(tableDef.Type, op.OldData) + if err != nil { + return fmt.Errorf("fail to decode old row at block: %d, error: %w", blockNum, err) + } + op.OldJSON = asMap + } + return nil +} + +func (s *StreamedAbiCodec) UpdateABI(blockNum uint32, step pbbstream.ForkStep, trxID string, actionTrace *pbcodec.ActionTrace) error { + zlog.Info("update abi", zap.Uint32("block_num", blockNum), zap.String("transaction_id", trxID)) + abi, err := decodeABIAtBlock(trxID, actionTrace) + if err != nil { + return fmt.Errorf("fail to decode abi error: %w", err) + } + s.resetCodecs() + + s.doUpdateABI(ABI{ + ABI: abi, + AbiBlockNum: blockNum, + Account: actionTrace.GetData("account").String(), + Irreversible: (step == pbbstream.ForkStep_STEP_UNDO), + }, blockNum, step) + return err +} + +func (s *StreamedAbiCodec) doUpdateABI(abi ABI, blockNum uint32, step pbbstream.ForkStep) { + if step == pbbstream.ForkStep_STEP_UNKNOWN { + zlog.Warn("skip ABI update on unknown step", zap.Uint32("block_num", blockNum), zap.Int32("step", int32(step))) + return + } + if step == pbbstream.ForkStep_STEP_UNDO { + if latestAbi, found := s.latestABIs[abi.Account]; found { + if blockNum > latestAbi.AbiBlockNum { // must have been replaced by a irreversible compaction + zlog.Info("undo skipped as undo block > latest abi block", zap.Uint32("undo_block_num", blockNum), zap.Uint32("latest_block_num", latestAbi.AbiBlockNum)) + return + } + zlog.Info("undo actual/latest ABI", zap.Uint32("undo_block_num", blockNum), zap.Uint32("latest_block_num", latestAbi.AbiBlockNum)) + delete(s.latestABIs, abi.Account) + if abiHistory, found := s.abiHistories[abi.Account]; found { + if len(abiHistory) > 0 { + // pop from history + previousAbi := abiHistory[len(abiHistory)-1] + zlog.Info("pop previous ABI from history on undo", zap.Uint32("undo_block_num", blockNum), zap.Uint32("previous_block_num", previousAbi.AbiBlockNum)) + s.latestABIs[abi.Account] = previousAbi + s.abiHistories[abi.Account] = abiHistory[:len(abiHistory)-1] + if len(s.abiHistories[abi.Account]) == 0 { // fix a testing compare issue + delete(s.abiHistories, abi.Account) + } + } + } else { + zlog.Info("nothing pop from history (empty) on undo", zap.Uint32("undo_block_num", blockNum)) + } + } else { + zlog.Warn("undo skipped no latest abi", zap.Uint32("undo_block_num", blockNum)) + return + } + } else { + newAbiItem := &abi + abiHistory := s.abiHistories[abi.Account] + latestABI := s.latestABIs[abi.Account] + if latestABI != nil { + if len(abiHistory) > 0 && abiHistory[len(abiHistory)-1].AbiBlockNum == latestABI.AbiBlockNum { + abiHistory[len(abiHistory)-1] = latestABI + } else { + abiHistory = append(abiHistory, latestABI) + } + s.abiHistories[abi.Account] = abiHistory + } + s.latestABIs[abi.Account] = newAbiItem + } +} + +func (s *StreamedAbiCodec) resetCodecs() { + zlog.Info("reset schema cache on reload static schema") + s.codecCache = s.initStaticSchemas(make(map[CodecId]Codec)) +} + +func (s *StreamedAbiCodec) initStaticSchemas(cache map[CodecId]Codec) map[CodecId]Codec { + for i, schema := range s.staticSchemas { + zlog.Info("register static schema", zap.Int("index", i), zap.String("name", schema.Name)) + s.registerStaticSchema(cache, schema) + } + return cache +} + +func (s *StreamedAbiCodec) registerStaticSchema(cache map[CodecId]Codec, schema MessageSchema) { + codec, err := s.newCodec(schema) + if err != nil { + zlog.Error("initStaticSchemas fail to create codec", zap.String("schema", schema.Name), zap.Error(err)) + panic(fmt.Sprintf("initStaticSchemas fail to create codec %s", schema.Name)) + } + cache[schema.AsCodecId()] = codec +} + +func (s *StreamedAbiCodec) setSubjectCompatibilityToForward(subject string) error { + compatibility := s.getCompatibility() + + zlog.Debug("set subject compatibility", zap.String("subject", subject), zap.String("compatibility", compatibility.String())) + _, err := s.schemaRegistryClient.ChangeSubjectCompatibilityLevel(subject, compatibility) + if err != nil { + if strings.HasPrefix(err.Error(), "mock") { + return nil + } + return fmt.Errorf("cannot change compatibility level of subject: '%s', error: %w", subject, err) + } + return err +} + +func (s *StreamedAbiCodec) getCompatibility() srclient.CompatibilityLevel { + var compatibility = s.compatibility + if compatibility == "" { + compatibility = DefaultCompatibility + } + return compatibility +} + +func (s *StreamedAbiCodec) newCodec(messageSchema MessageSchema) (Codec, error) { + messageSchema.Meta.Compatibility = s.getCompatibility().String() + subject := fmt.Sprintf("%s.%s", messageSchema.Namespace, messageSchema.Name) + jsonSchema, err := json.Marshal(messageSchema) + if err != nil { + return nil, err + } + zlog.Debug("register schema", zap.String("subject", subject), zap.ByteString("schema", jsonSchema)) + zlog.Debug("get compatibility level of subject's schema", zap.String("subject", subject)) + actualCompatibilityLevel, err := s.schemaRegistryClient.GetCompatibilityLevel(subject, true) + unknownSubject := false + if err != nil { + unknownSubject = true + } else if *actualCompatibilityLevel != s.getCompatibility() { + err = s.setSubjectCompatibilityToForward(subject) + if err != nil { + return nil, err + } + } + schema, err := s.schemaRegistryClient.CreateSchema(subject, string(jsonSchema), srclient.Avro) + if err != nil { + return nil, fmt.Errorf("CreateSchema on subject: '%s', schema:\n%s error: %w", subject, string(jsonSchema), err) + } + if unknownSubject { + err = s.setSubjectCompatibilityToForward(subject) + if err != nil { + return nil, err + } + } + + zlog.Debug("create kafka avro codec", zap.String("subject", subject), zap.Int("ID", schema.ID())) + ac, err := goavro.NewCodecWithConverters(schema.Schema(), schemaTypeConverters) + if err != nil { + return nil, fmt.Errorf("goavro.NewCodecWithConverters error: %w, with schema %s", err, string(jsonSchema)) + } + codec := NewKafkaAvroCodec(s.schemaRegistryURL, schema, ac) + return codec, nil +} + +func decodeABIAtBlock(trxID string, actionTrace *pbcodec.ActionTrace) (*eos.ABI, error) { + account := actionTrace.GetData("account").String() + hexABI := actionTrace.GetData("abi") + if !hexABI.Exists() { + zlog.Error("'setabi' action data payload not present", zap.String("account", account), zap.String("transaction_id", trxID)) + return nil, fmt.Errorf("'setabi' action data payload not present. account: %s, transaction_id: %s ", account, trxID) + } + + hexData := hexABI.String() + return DecodeABI(trxID, account, hexData) +} diff --git a/streamedabicodec_test.go b/streamedabicodec_test.go new file mode 100644 index 0000000..e1489c9 --- /dev/null +++ b/streamedabicodec_test.go @@ -0,0 +1,831 @@ +package dkafka + +import ( + "context" + "fmt" + "reflect" + "testing" + + pbabicodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/abicodec/v1" + "github.com/eoscanada/eos-go" + "github.com/go-test/deep" + "github.com/riferrei/srclient" + pbbstream "github.com/streamingfast/pbgo/dfuse/bstream/v1" + "google.golang.org/grpc" +) + +type DecoderClientStub struct { + err error + response *pbabicodec.Response +} + +func (d *DecoderClientStub) DecodeTable(ctx context.Context, in *pbabicodec.DecodeTableRequest, opts ...grpc.CallOption) (r *pbabicodec.Response, e error) { + return +} +func (d *DecoderClientStub) DecodeAction(ctx context.Context, in *pbabicodec.DecodeActionRequest, opts ...grpc.CallOption) (r *pbabicodec.Response, e error) { + return +} +func (d *DecoderClientStub) GetAbi(ctx context.Context, in *pbabicodec.GetAbiRequest, opts ...grpc.CallOption) (*pbabicodec.Response, error) { + return d.response, d.err +} + +func TestDfuseAbiRepository_GetAbi(t *testing.T) { + type args struct { + contract string + blockNum uint32 + } + tests := []struct { + name string + sut *DfuseAbiRepository + args args + want *ABI + wantErr bool + }{ + { + name: "overrides", + sut: &DfuseAbiRepository{ + overrides: map[string]*ABI{"test": { + ABI: &eos.ABI{Version: "1.2.3"}, + AbiBlockNum: 42, + }}, + abiCodecCli: nil, + context: nil, + }, + args: args{ + contract: "test", + blockNum: 101, + }, + want: &ABI{ + ABI: &eos.ABI{Version: "1.2.3"}, + AbiBlockNum: 42, + }, + wantErr: false, + }, + { + name: "error-missing-client", + sut: &DfuseAbiRepository{ + overrides: nil, + abiCodecCli: nil, + context: nil, + }, + args: args{ + contract: "test", + blockNum: 101, + }, + want: nil, + wantErr: true, + }, + { + name: "error-dfuse-call", + sut: &DfuseAbiRepository{ + overrides: nil, + abiCodecCli: &DecoderClientStub{ + err: fmt.Errorf("test"), + }, + context: nil, + }, + args: args{ + contract: "test", + blockNum: 101, + }, + want: nil, + wantErr: true, + }, + { + name: "error-dfuse-response-decode", + sut: &DfuseAbiRepository{ + overrides: nil, + abiCodecCli: &DecoderClientStub{ + err: nil, + response: &pbabicodec.Response{ + AbiBlockNum: 42, + JsonPayload: "invalid json", + }, + }, + context: nil, + }, + args: args{ + contract: "test", + blockNum: 101, + }, + want: nil, + wantErr: true, + }, + { + name: "valid-dfuse-response", + sut: &DfuseAbiRepository{ + overrides: nil, + abiCodecCli: &DecoderClientStub{ + err: nil, + response: &pbabicodec.Response{ + AbiBlockNum: 42, + JsonPayload: "{\"version\":\"1.2.3\"}", + }, + }, + context: nil, + }, + args: args{ + contract: "test", + blockNum: 101, + }, + want: &ABI{ + ABI: &eos.ABI{Version: "1.2.3"}, + Account: "test", + AbiBlockNum: 42, + Irreversible: true, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := tt.sut + got, err := a.GetAbi(tt.args.contract, tt.args.blockNum) + if (err != nil) != tt.wantErr { + t.Errorf("DfuseAbiRepository.GetAbi() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("DfuseAbiRepository.GetAbi() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDfuseAbiRepository_IsNOOP(t *testing.T) { + tests := []struct { + name string + sut *DfuseAbiRepository + want bool + }{ + { + name: "noop", + sut: &DfuseAbiRepository{}, + want: true, + }, + { + name: "noop", + sut: &DfuseAbiRepository{ + overrides: make(map[string]*ABI), + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := tt.sut + if got := b.IsNOOP(); got != tt.want { + t.Errorf("DfuseAbiRepository.IsNOOP() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStreamedABICodec_doUpdateABI(t *testing.T) { + type args struct { + abi ABI + blockNum uint32 + step pbbstream.ForkStep + } + tests := []struct { + name string + sut *StreamedAbiCodec + args args + want *StreamedAbiCodec + }{ + { + name: "empty-irreversible", + sut: &StreamedAbiCodec{ + latestABIs: make(map[string]*ABI), + abiHistories: make(map[string][]*ABI), + codecCache: make(map[CodecId]Codec), + }, + args: args{ + abi: ABI{ + ABI: &eos.ABI{Version: "123"}, + AbiBlockNum: 42, + Account: "eosio", + Irreversible: true, + }, + blockNum: 42, + step: pbbstream.ForkStep_STEP_IRREVERSIBLE, + }, + want: &StreamedAbiCodec{ + latestABIs: map[string]*ABI{"eosio": { + ABI: &eos.ABI{Version: "123"}, + AbiBlockNum: 42, + Account: "eosio", + Irreversible: true, + }, + }, + abiHistories: make(map[string][]*ABI), + codecCache: make(map[CodecId]Codec), + }, + }, + { + name: "empty-new", + sut: &StreamedAbiCodec{ + latestABIs: make(map[string]*ABI), + abiHistories: make(map[string][]*ABI), + codecCache: make(map[CodecId]Codec), + }, + args: args{ + abi: ABI{ + ABI: &eos.ABI{Version: "123"}, + AbiBlockNum: 42, + Account: "eosio", + Irreversible: false, + }, + blockNum: 42, + step: pbbstream.ForkStep_STEP_NEW, + }, + want: &StreamedAbiCodec{ + latestABIs: map[string]*ABI{"eosio": { + ABI: &eos.ABI{Version: "123"}, + AbiBlockNum: 42, + Account: "eosio", + Irreversible: false, + }, + }, + abiHistories: make(map[string][]*ABI), + codecCache: make(map[CodecId]Codec), + }, + }, + { + name: "not-empty-irreversible", + sut: &StreamedAbiCodec{ + latestABIs: map[string]*ABI{"eosio": { + ABI: &eos.ABI{Version: "456"}, + AbiBlockNum: 42, + Account: "eosio", + Irreversible: false, + }, + }, + abiHistories: map[string][]*ABI{"eosio": {{ABI: &eos.ABI{Version: "123"}, + AbiBlockNum: 1, + Irreversible: true, + Account: "eosio", + }}}, + codecCache: make(map[CodecId]Codec), + }, + args: args{ + abi: ABI{ + ABI: &eos.ABI{Version: "456"}, + AbiBlockNum: 42, + Account: "eosio", + Irreversible: true, + }, + blockNum: 42, + step: pbbstream.ForkStep_STEP_IRREVERSIBLE, + }, + want: &StreamedAbiCodec{ + latestABIs: map[string]*ABI{"eosio": { + ABI: &eos.ABI{Version: "456"}, + AbiBlockNum: 42, + Irreversible: true, + Account: "eosio", + }, + }, + abiHistories: map[string][]*ABI{"eosio": {{ + ABI: &eos.ABI{Version: "123"}, + AbiBlockNum: 1, + Irreversible: true, + Account: "eosio", + }, + { + ABI: &eos.ABI{Version: "456"}, + AbiBlockNum: 42, + Irreversible: false, + Account: "eosio", + }, + }, + }, + codecCache: make(map[CodecId]Codec), + }, + }, + { + name: "irreversible-compaction", + sut: &StreamedAbiCodec{ + latestABIs: map[string]*ABI{"eosio": { + ABI: &eos.ABI{Version: "456"}, + AbiBlockNum: 42, + Irreversible: true, + Account: "eosio", + }}, + abiHistories: map[string][]*ABI{"eosio": {{ + ABI: &eos.ABI{Version: "123"}, + AbiBlockNum: 1, + Irreversible: true, + Account: "eosio", + }, + { + ABI: &eos.ABI{Version: "456"}, + AbiBlockNum: 42, + Irreversible: false, + Account: "eosio", + }, + }, + }, + }, + args: args{ + abi: ABI{ + ABI: &eos.ABI{Version: "789"}, + AbiBlockNum: 64, + Account: "eosio", + Irreversible: false, + }, + blockNum: 64, + step: pbbstream.ForkStep_STEP_NEW, + }, + want: &StreamedAbiCodec{ + latestABIs: map[string]*ABI{"eosio": { + ABI: &eos.ABI{Version: "789"}, + AbiBlockNum: 64, + Irreversible: false, + Account: "eosio", + }, + }, + abiHistories: map[string][]*ABI{"eosio": {{ + ABI: &eos.ABI{Version: "123"}, + AbiBlockNum: 1, + Irreversible: true, + Account: "eosio", + }, + { + ABI: &eos.ABI{Version: "456"}, + AbiBlockNum: 42, + Irreversible: true, + Account: "eosio", + }, + }, + }, + }, + }, + { + name: "irreversible-only", + sut: &StreamedAbiCodec{ + latestABIs: map[string]*ABI{"eosio": { + ABI: &eos.ABI{Version: "456"}, + AbiBlockNum: 42, + Irreversible: true, + Account: "eosio", + }, + }, + abiHistories: map[string][]*ABI{"eosio": {{ + ABI: &eos.ABI{Version: "123"}, + AbiBlockNum: 1, + Irreversible: true, + Account: "eosio", + }, + }, + }, + }, + args: args{ + // abi: &eos.ABI{Version: "789"}, + abi: ABI{ + ABI: &eos.ABI{Version: "789"}, + AbiBlockNum: 64, + Account: "eosio", + Irreversible: true, + }, + blockNum: 64, + step: pbbstream.ForkStep_STEP_IRREVERSIBLE, + }, + want: &StreamedAbiCodec{ + latestABIs: map[string]*ABI{"eosio": { + ABI: &eos.ABI{Version: "789"}, + AbiBlockNum: 64, + Irreversible: true, + Account: "eosio", + }, + }, + abiHistories: map[string][]*ABI{"eosio": { + { + ABI: &eos.ABI{Version: "123"}, + AbiBlockNum: 1, + Irreversible: true, + Account: "eosio", + }, + { + ABI: &eos.ABI{Version: "456"}, + AbiBlockNum: 42, + Irreversible: true, + Account: "eosio", + }, + }, + }, + }, + }, + { + name: "undo", + sut: &StreamedAbiCodec{ + latestABIs: map[string]*ABI{"eosio": { + ABI: &eos.ABI{Version: "456"}, + AbiBlockNum: 42, + Irreversible: false, + Account: "eosio", + }, + }, + abiHistories: map[string][]*ABI{"eosio": { + { + ABI: &eos.ABI{Version: "123"}, + AbiBlockNum: 1, + Irreversible: true, + Account: "eosio", + }, + }, + }, + }, + args: args{ + abi: ABI{ + ABI: &eos.ABI{Version: "456"}, + AbiBlockNum: 42, + Account: "eosio", + Irreversible: false, + }, + + blockNum: 42, + step: pbbstream.ForkStep_STEP_UNDO, + }, + want: &StreamedAbiCodec{ + latestABIs: map[string]*ABI{"eosio": { + ABI: &eos.ABI{Version: "123"}, + AbiBlockNum: 1, + Irreversible: true, + Account: "eosio", + }, + }, + abiHistories: make(map[string][]*ABI), + }, + }, + { + name: "unknown", + sut: &StreamedAbiCodec{ + latestABIs: map[string]*ABI{"eosio": { + ABI: &eos.ABI{Version: "456"}, + AbiBlockNum: 42, + Irreversible: false, + Account: "eosio", + }, + }, + abiHistories: map[string][]*ABI{"eosio": { + { + ABI: &eos.ABI{Version: "123"}, + AbiBlockNum: 1, + Irreversible: true, + Account: "eosio", + }, + }, + }, + }, + args: args{ + abi: ABI{ + ABI: &eos.ABI{Version: "456"}, + AbiBlockNum: 42, + Account: "eosio", + Irreversible: false, + }, + blockNum: 42, + step: pbbstream.ForkStep_STEP_UNKNOWN, + }, + want: &StreamedAbiCodec{ + latestABIs: map[string]*ABI{"eosio": { + ABI: &eos.ABI{Version: "456"}, + AbiBlockNum: 42, + Irreversible: false, + Account: "eosio", + }, + }, + abiHistories: map[string][]*ABI{"eosio": { + { + ABI: &eos.ABI{Version: "123"}, + AbiBlockNum: 1, + Irreversible: true, + Account: "eosio", + }, + }, + }, + }, + }, + { + name: "undo-empty", + sut: &StreamedAbiCodec{ + latestABIs: make(map[string]*ABI), + abiHistories: make(map[string][]*ABI), + }, + args: args{ + // abi: &eos.ABI{Version: "456"}, + abi: ABI{ + ABI: &eos.ABI{Version: "456"}, + AbiBlockNum: 42, + Account: "eosio", + Irreversible: false, + }, + blockNum: 42, + step: pbbstream.ForkStep_STEP_UNDO, + }, + want: &StreamedAbiCodec{ + latestABIs: make(map[string]*ABI), + abiHistories: make(map[string][]*ABI), + }, + }, + { + name: "undo-gt-latest", + sut: &StreamedAbiCodec{ + latestABIs: map[string]*ABI{"eosio": { + ABI: &eos.ABI{Version: "123"}, + AbiBlockNum: 24, + Irreversible: true, + Account: "eosio", + }, + }, + abiHistories: make(map[string][]*ABI), + }, + args: args{ + abi: ABI{ + ABI: &eos.ABI{Version: "456"}, + AbiBlockNum: 42, + Account: "eosio", + Irreversible: false, + }, + blockNum: 42, + step: pbbstream.ForkStep_STEP_UNDO, + }, + want: &StreamedAbiCodec{ + latestABIs: map[string]*ABI{"eosio": { + ABI: &eos.ABI{Version: "123"}, + AbiBlockNum: 24, + Irreversible: true, + Account: "eosio", + }, + }, + abiHistories: make(map[string][]*ABI), + }, + }, + { + name: "undo-long-history", + sut: &StreamedAbiCodec{ + latestABIs: map[string]*ABI{"eosio": { + ABI: &eos.ABI{Version: "789"}, + AbiBlockNum: 42, + Irreversible: false, + Account: "eosio", + }, + }, + abiHistories: map[string][]*ABI{"eosio": { + { + ABI: &eos.ABI{Version: "123"}, + AbiBlockNum: 1, + Irreversible: true, + Account: "eosio", + }, + { + ABI: &eos.ABI{Version: "456"}, + AbiBlockNum: 24, + Irreversible: true, + Account: "eosio", + }, + }, + }, + }, + args: args{ + // abi: &eos.ABI{Version: "789"}, + abi: ABI{ + ABI: &eos.ABI{Version: "789"}, + AbiBlockNum: 42, + Account: "eosio", + Irreversible: false, + }, + blockNum: 42, + step: pbbstream.ForkStep_STEP_UNDO, + }, + want: &StreamedAbiCodec{ + latestABIs: map[string]*ABI{"eosio": { + ABI: &eos.ABI{Version: "456"}, + AbiBlockNum: 24, + Irreversible: true, + Account: "eosio", + }, + }, + abiHistories: map[string][]*ABI{"eosio": { + { + ABI: &eos.ABI{Version: "123"}, + AbiBlockNum: 1, + Irreversible: true, + Account: "eosio", + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.sut.doUpdateABI(tt.args.abi, tt.args.blockNum, tt.args.step) + // assertEqual(t, tt.sut, tt.want) + deep.CompareUnexportedFields = true + if diff := deep.Equal(tt.sut, tt.want); diff != nil { + t.Error(diff) + } + }) + } +} + +type AbiRepositoryStub struct { + noop bool + abi *ABI + err error +} + +func (s *AbiRepositoryStub) IsNOOP() bool { + return s.noop +} + +func (s *AbiRepositoryStub) GetAbi(contract string, blockNum uint32) (*ABI, error) { + return s.abi, s.err +} + +func TestStreamedAbiCodec_IsNOOP(t *testing.T) { + + tests := []struct { + name string + sut *StreamedAbiCodec + want bool + }{ + { + name: "noop", + sut: &StreamedAbiCodec{ + bootstrapper: &AbiRepositoryStub{ + noop: false, + }, + }, + want: false, + }, + { + name: "operational", + sut: &StreamedAbiCodec{ + bootstrapper: &AbiRepositoryStub{ + noop: true, + }, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := tt.sut + if got := s.IsNOOP(); got != tt.want { + t.Errorf("StreamedAbiCodec.IsNOOP() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStreamedAbiCodec_GetCodec(t *testing.T) { + var localABIFiles = map[string]string{ + "eosio.nft.ft": "testdata/eosio.nft.ft.abi:1", + } + abiFiles, _ := LoadABIFiles(localABIFiles) + + dummyCodec := NewJSONCodec() + msg := MessageSchemaGenerator{ + Namespace: "test", + Version: "", + Account: "eosio.nft.ft", + } + type args struct { + name CodecId + blockNum uint32 + } + tests := []struct { + name string + sut ABICodec + args args + want Codec + wantErr bool + }{ + { + name: "cached-codec", + sut: &StreamedAbiCodec{ + bootstrapper: nil, + latestABIs: make(map[string]*ABI), + abiHistories: make(map[string][]*ABI), + getSchema: func(string, *ABI) (MessageSchema, error) { + return MessageSchema{}, nil + }, + schemaRegistryClient: nil, + account: "test", + codecCache: map[CodecId]Codec{{"eosio.nft.ft", "TestTable"}: dummyCodec}, + schemaRegistryURL: "http://localhost:8083", + }, + args: args{ + name: CodecId{"eosio.nft.ft", "TestTable"}, + blockNum: 42, + }, + want: dummyCodec, + wantErr: false, + }, + { + name: "bootstrap-abi-error", + sut: &StreamedAbiCodec{ + bootstrapper: &AbiRepositoryStub{ + abi: nil, + err: fmt.Errorf("bootstrap-abi-error"), + }, + latestABIs: make(map[string]*ABI), + abiHistories: make(map[string][]*ABI), + getSchema: func(string, *ABI) (MessageSchema, error) { + return MessageSchema{}, nil + }, + schemaRegistryClient: nil, + account: "test", + codecCache: map[CodecId]Codec{}, + schemaRegistryURL: "http://localhost:8083", + }, + args: args{ + name: CodecId{"eosio.nft.ft", "TestTable"}, + blockNum: 42, + }, + want: nil, + wantErr: true, + }, + { + name: "dkafka-checkpoint-codec", + sut: NewStreamedAbiCodec( + &AbiRepositoryStub{ + abi: nil, + err: nil, + }, + msg.getTableSchema, + srclient.CreateMockSchemaRegistryClient("mock://TestKafkaAvroABICodec_GetCodec"), + "eosio.nft.ft", + "mock://TestKafkaAvroABICodec_GetCodec", + srclient.Forward, + ), + args: args{ + name: CheckpointSchema.AsCodecId(), + blockNum: 42, + }, + want: &KafkaAvroCodec{ + schemaURLTemplate: "mock://TestKafkaAvroABICodec_GetCodec/schemas/ids/%d", + schema: RegisteredSchema{ + id: 1, + schema: "{\"type\":\"record\",\"name\":\"DKafkaCheckpoint\",\"namespace\":\"io.dkafka\",\"doc\":\"Periodically emitted checkpoint used to save the current position\",\"fields\":[{\"name\":\"step\",\"doc\":\"Step of the current block value can be: 1(New),2(Undo),3(Redo),4(Handoff),5(Irreversible),6(Stalled)\\n - 1(New): First time we're seeing this block\\n - 2(Undo): We are undoing this block (it was done previously)\\n - 4(Redo): We are redoing this block (it was done previously)\\n - 8(Handoff): The block passed a handoff from one producer to another\\n - 16(Irreversible): This block passed the LIB barrier and is in chain\\n - 32(Stalled): This block passed the LIB and is definitely forked out\\n\",\"type\":\"int\"},{\"name\":\"block\",\"type\":{\"type\":\"record\",\"name\":\"BlockRef\",\"namespace\":\"io.dkafka\",\"doc\":\"BlockRef represents a reference to a block and is mainly define as the pair \\u003cBlockID, BlockNum\\u003e\",\"fields\":[{\"name\":\"id\",\"type\":\"string\"},{\"name\":\"num\",\"type\":\"long\"}]}},{\"name\":\"headBlock\",\"type\":\"BlockRef\"},{\"name\":\"lastIrreversibleBlock\",\"type\":\"BlockRef\"},{\"name\":\"time\",\"type\":{\"logicalType\":\"timestamp-millis\",\"type\":\"long\"}}],\"meta\":{\"compatibility\":\"FORWARD\",\"type\":\"notification\",\"version\":\"1.0.0\",\"domain\":\"dkafka\"}}", + version: 1, + }, + }, + wantErr: false, + }, + { + name: "factory.a-codec", + sut: NewStreamedAbiCodec( + &DfuseAbiRepository{ + overrides: abiFiles, + abiCodecCli: nil, + context: nil, + }, + msg.getTableSchema, + srclient.CreateMockSchemaRegistryClient("mock://TestKafkaAvroABICodec_GetCodec"), + "eosio.nft.ft", + "mock://TestKafkaAvroABICodec_GetCodec", + srclient.Forward, + ), + args: args{ + name: CodecId{"eosio.nft.ft", "factory.a"}, + blockNum: 42, + }, + want: &KafkaAvroCodec{ + schemaURLTemplate: "mock://TestKafkaAvroABICodec_GetCodec/schemas/ids/%d", + schema: RegisteredSchema{ + id: 2, + schema: "{\"type\":\"record\",\"name\":\"FactoryATableNotification\",\"namespace\":\"test.eosio.nft.ft.tables.v0\",\"fields\":[{\"name\":\"context\",\"type\":{\"type\":\"record\",\"name\":\"NotificationContext\",\"namespace\":\"io.dkafka\",\"fields\":[{\"name\":\"block_num\",\"type\":\"long\"},{\"name\":\"block_id\",\"type\":\"string\"},{\"name\":\"status\",\"type\":\"string\"},{\"name\":\"executed\",\"type\":\"boolean\"},{\"name\":\"block_step\",\"type\":\"string\"},{\"name\":\"correlation\",\"type\":[\"null\",{\"type\":\"record\",\"name\":\"Correlation\",\"namespace\":\"io.dkafka\",\"fields\":[{\"name\":\"payer\",\"type\":\"string\"},{\"name\":\"id\",\"type\":\"string\"}]}],\"default\":null},{\"name\":\"trx_id\",\"type\":\"string\"},{\"name\":\"time\",\"type\":{\"eos.type\":\"block_timestamp_type\",\"logicalType\":\"timestamp-millis\",\"type\":\"long\"}},{\"name\":\"cursor\",\"type\":\"string\"}]}},{\"name\":\"action\",\"type\":{\"type\":\"record\",\"name\":\"ActionInfoBasic\",\"namespace\":\"io.dkafka\",\"fields\":[{\"name\":\"account\",\"type\":\"string\"},{\"name\":\"receiver\",\"type\":\"string\"},{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"global_seq\",\"type\":\"long\"},{\"name\":\"authorizations\",\"type\":{\"type\":\"array\",\"items\":\"string\"}},{\"name\":\"action_ordinal\",\"type\":\"long\"},{\"name\":\"creator_action_ordinal\",\"type\":\"long\"},{\"name\":\"closest_unnotified_ancestor_action_ordinal\",\"type\":\"long\"},{\"name\":\"execution_index\",\"type\":\"long\"}]}},{\"name\":\"db_op\",\"type\":{\"type\":\"record\",\"name\":\"FactoryATableOpInfo\",\"fields\":[{\"name\":\"operation\",\"type\":[\"null\",\"int\"],\"default\":null},{\"name\":\"action_index\",\"type\":[\"null\",\"long\"],\"default\":null},{\"name\":\"index\",\"type\":\"int\"},{\"name\":\"code\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"scope\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"table_name\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"primary_key\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"old_payer\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"new_payer\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"old_data\",\"type\":[\"null\",\"bytes\"],\"default\":null},{\"name\":\"new_data\",\"type\":[\"null\",\"bytes\"],\"default\":null},{\"name\":\"old_json\",\"type\":[\"null\",{\"type\":\"record\",\"name\":\"FactoryATableOp\",\"fields\":[{\"name\":\"id\",\"type\":{\"eos.type\":\"uint64\",\"logicalType\":\"eos.uint64\",\"type\":\"long\"}},{\"name\":\"asset_manager\",\"type\":{\"eos.type\":\"name\",\"type\":\"string\"}},{\"name\":\"asset_creator\",\"type\":{\"eos.type\":\"name\",\"type\":\"string\"}},{\"name\":\"conversion_rate_oracle_contract\",\"type\":{\"eos.type\":\"name\",\"type\":\"string\"}},{\"name\":\"chosen_rate\",\"type\":{\"type\":\"array\",\"items\":{\"type\":\"record\",\"name\":\"Asset\",\"namespace\":\"eosio\",\"convert\":\"eosio.Asset\",\"fields\":[{\"name\":\"amount\",\"type\":{\"type\":\"bytes\",\"logicalType\":\"decimal\",\"precision\":32,\"scale\":8}},{\"name\":\"symbol\",\"type\":\"string\"},{\"name\":\"precision\",\"type\":\"int\"}]}}},{\"name\":\"minimum_resell_price\",\"type\":\"eosio.Asset\"},{\"name\":\"resale_shares\",\"type\":{\"type\":\"array\",\"items\":{\"type\":\"record\",\"name\":\"ResaleShare\",\"fields\":[{\"name\":\"receiver\",\"type\":{\"eos.type\":\"name\",\"type\":\"string\"}},{\"name\":\"basis_point\",\"type\":{\"eos.type\":\"uint16\",\"type\":\"int\"}}]}}},{\"name\":\"mintable_window_start\",\"type\":[\"null\",{\"eos.type\":\"uint32\",\"type\":\"long\"}],\"default\":null},{\"name\":\"mintable_window_end\",\"type\":[\"null\",{\"eos.type\":\"uint32\",\"type\":\"long\"}],\"default\":null},{\"name\":\"trading_window_start\",\"type\":[\"null\",{\"eos.type\":\"uint32\",\"type\":\"long\"}],\"default\":null},{\"name\":\"trading_window_end\",\"type\":[\"null\",{\"eos.type\":\"uint32\",\"type\":\"long\"}],\"default\":null},{\"name\":\"recall_window_start\",\"type\":[\"null\",{\"eos.type\":\"uint32\",\"type\":\"long\"}],\"default\":null},{\"name\":\"recall_window_end\",\"type\":[\"null\",{\"eos.type\":\"uint32\",\"type\":\"long\"}],\"default\":null},{\"name\":\"lockup_time\",\"type\":[\"null\",{\"eos.type\":\"uint32\",\"type\":\"long\"}],\"default\":null},{\"name\":\"conditionless_receivers\",\"type\":{\"type\":\"array\",\"items\":{\"eos.type\":\"name\",\"type\":\"string\"}}},{\"name\":\"stat\",\"type\":{\"eos.type\":\"uint8\",\"type\":\"int\"}},{\"name\":\"meta_uris\",\"type\":{\"type\":\"array\",\"items\":{\"eos.type\":\"string\",\"type\":\"string\"}}},{\"name\":\"meta_hash\",\"type\":{\"eos.type\":\"checksum256\",\"type\":\"bytes\"}},{\"name\":\"max_mintable_tokens\",\"type\":[\"null\",{\"eos.type\":\"uint32\",\"type\":\"long\"}],\"default\":null},{\"name\":\"minted_tokens_no\",\"type\":{\"eos.type\":\"uint32\",\"type\":\"long\"}},{\"name\":\"existing_tokens_no\",\"type\":{\"eos.type\":\"uint32\",\"type\":\"long\"}}]}],\"default\":null},{\"name\":\"new_json\",\"type\":[\"null\",\"FactoryATableOp\"],\"default\":null}]}}],\"meta\":{\"compatibility\":\"FORWARD\",\"type\":\"notification\",\"version\":\"0.1.0\",\"domain\":\"eosio.nft.ft\"}}", + version: 1, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := tt.sut + got, err := s.GetCodec(tt.args.name, tt.args.blockNum) + if (err != nil) != tt.wantErr { + t.Errorf("StreamedAbiCodec.GetCodec() error = %v, wantErr %v", err, tt.wantErr) + return + } + expectedAvroCodec, ok := tt.want.(*KafkaAvroCodec) + if ok { + actualAvroCodec := got.(KafkaAvroCodec) + deep.CompareUnexportedFields = true + if diff := deep.Equal(expectedAvroCodec, &actualAvroCodec); diff != nil { + t.Error(diff) + } + if actualAvroCodec.schema.codec == nil { + t.Error("KafkaAvroCodec.schema.codec should not be nil") + } + } else if !reflect.DeepEqual(got, tt.want) { + t.Errorf("StreamedAbiCodec.GetCodec() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/table/filter.go b/table/filter.go new file mode 100644 index 0000000..d15d16a --- /dev/null +++ b/table/filter.go @@ -0,0 +1,7 @@ +package table + +import "fmt" + +func Filter(account string) string { + return fmt.Sprintf("account==\"%s\"", account) +} diff --git a/table/filter_test.go b/table/filter_test.go new file mode 100644 index 0000000..23dd267 --- /dev/null +++ b/table/filter_test.go @@ -0,0 +1,36 @@ +package table + +import "testing" + +func TestFilter(t *testing.T) { + type args struct { + account string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "eosio.token", + args: args{ + account: "eosio.token", + }, + want: `account=="eosio.token"`, + }, + { + name: "eosio.eba", + args: args{ + account: "eosio.eba", + }, + want: `account=="eosio.eba"`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Filter(tt.args.account); got != tt.want { + t.Errorf("Filter() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/testdata/1aa2aa3aa4bx.abi b/testdata/1aa2aa3aa4bx.abi new file mode 100644 index 0000000..4f11a9f --- /dev/null +++ b/testdata/1aa2aa3aa4bx.abi @@ -0,0 +1,415 @@ +{ + "version": "eosio::abi/1.1", + "structs": [ + { + "name": "account", + "base": "", + "fields": [ + { + "name": "balance", + "type": "asset" + } + ] + }, + { + "name": "addliquidity", + "base": "", + "fields": [ + { + "name": "user", + "type": "name" + }, + { + "name": "to_buy", + "type": "asset" + }, + { + "name": "max_asset1", + "type": "asset" + }, + { + "name": "max_asset2", + "type": "asset" + } + ] + }, + { + "name": "changefee", + "base": "", + "fields": [ + { + "name": "pair_token", + "type": "symbol_code" + }, + { + "name": "newfee", + "type": "int32" + } + ] + }, + { + "name": "close", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "symbol", + "type": "symbol" + } + ] + }, + { + "name": "closeext", + "base": "", + "fields": [ + { + "name": "user", + "type": "name" + }, + { + "name": "to", + "type": "name" + }, + { + "name": "ext_symbol", + "type": "extended_symbol" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "currency_stats", + "base": "", + "fields": [ + { + "name": "supply", + "type": "asset" + }, + { + "name": "max_supply", + "type": "asset" + }, + { + "name": "issuer", + "type": "name" + }, + { + "name": "pool1", + "type": "extended_asset" + }, + { + "name": "pool2", + "type": "extended_asset" + }, + { + "name": "fee", + "type": "int32" + }, + { + "name": "fee_contract", + "type": "name" + } + ] + }, + { + "name": "evodexaccount", + "base": "", + "fields": [ + { + "name": "balance", + "type": "extended_asset" + }, + { + "name": "id", + "type": "uint64" + } + ] + }, + { + "name": "exchange", + "base": "", + "fields": [ + { + "name": "user", + "type": "name" + }, + { + "name": "pair_token", + "type": "symbol_code" + }, + { + "name": "ext_asset_in", + "type": "extended_asset" + }, + { + "name": "min_expected", + "type": "asset" + } + ] + }, + { + "name": "extended_symbol", + "base": "", + "fields": [ + { + "name": "sym", + "type": "symbol" + }, + { + "name": "contract", + "type": "name" + } + ] + }, + { + "name": "index_struct", + "base": "", + "fields": [ + { + "name": "evo_symbol", + "type": "symbol" + }, + { + "name": "id_256", + "type": "checksum256" + } + ] + }, + { + "name": "indexpair", + "base": "", + "fields": [ + { + "name": "user", + "type": "name" + }, + { + "name": "evo_symbol", + "type": "symbol" + } + ] + }, + { + "name": "inittoken", + "base": "", + "fields": [ + { + "name": "user", + "type": "name" + }, + { + "name": "new_symbol", + "type": "symbol" + }, + { + "name": "initial_pool1", + "type": "extended_asset" + }, + { + "name": "initial_pool2", + "type": "extended_asset" + }, + { + "name": "initial_fee", + "type": "int32" + }, + { + "name": "fee_contract", + "type": "name" + } + ] + }, + { + "name": "open", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "symbol", + "type": "symbol" + }, + { + "name": "ram_payer", + "type": "name" + } + ] + }, + { + "name": "openext", + "base": "", + "fields": [ + { + "name": "user", + "type": "name" + }, + { + "name": "payer", + "type": "name" + }, + { + "name": "ext_symbol", + "type": "extended_symbol" + } + ] + }, + { + "name": "remliquidity", + "base": "", + "fields": [ + { + "name": "user", + "type": "name" + }, + { + "name": "to_sell", + "type": "asset" + }, + { + "name": "min_asset1", + "type": "asset" + }, + { + "name": "min_asset2", + "type": "asset" + } + ] + }, + { + "name": "transfer", + "base": "", + "fields": [ + { + "name": "from", + "type": "name" + }, + { + "name": "to", + "type": "name" + }, + { + "name": "quantity", + "type": "asset" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "withdraw", + "base": "", + "fields": [ + { + "name": "user", + "type": "name" + }, + { + "name": "to", + "type": "name" + }, + { + "name": "to_withdraw", + "type": "extended_asset" + }, + { + "name": "memo", + "type": "string" + } + ] + } + ], + "actions": [ + { + "name": "addliquidity", + "type": "addliquidity", + "ricardian_contract": "" + }, + { + "name": "changefee", + "type": "changefee", + "ricardian_contract": "" + }, + { + "name": "close", + "type": "close", + "ricardian_contract": "" + }, + { + "name": "closeext", + "type": "closeext", + "ricardian_contract": "" + }, + { + "name": "exchange", + "type": "exchange", + "ricardian_contract": "" + }, + { + "name": "indexpair", + "type": "indexpair", + "ricardian_contract": "" + }, + { + "name": "inittoken", + "type": "inittoken", + "ricardian_contract": "" + }, + { + "name": "open", + "type": "open", + "ricardian_contract": "" + }, + { + "name": "openext", + "type": "openext", + "ricardian_contract": "" + }, + { + "name": "remliquidity", + "type": "remliquidity", + "ricardian_contract": "" + }, + { + "name": "transfer", + "type": "transfer", + "ricardian_contract": "" + }, + { + "name": "withdraw", + "type": "withdraw", + "ricardian_contract": "" + } + ], + "tables": [ + { + "name": "accounts", + "index_type": "i64", + "type": "account" + }, + { + "name": "evodexacnts", + "index_type": "i64", + "type": "evodexaccount" + }, + { + "name": "evoindex", + "index_type": "i64", + "type": "index_struct" + }, + { + "name": "stat", + "index_type": "i64", + "type": "currency_stats" + } + ] +} \ No newline at end of file diff --git a/testdata/abi.hex b/testdata/abi.hex new file mode 100644 index 0000000..898fc79 --- /dev/null +++ b/testdata/abi.hex @@ -0,0 +1 @@ +0e656f73696f3a3a6162692f312e31080c61737365745f766563746f720761737365745b5d1969737375655f746f6b656e5f636f6e6669675f766563746f721469737375655f746f6b656e5f636f6e6669675b5d1b6d696e7465725f617574686f72697a6174696f6e5f766563746f721b6d696e7465725f617574686f72697a6174696f6e5f696e666f5b5d0b6e616d655f766563746f72066e616d655b5d13726573616c655f73686172655f766563746f720e726573616c655f73686172655b5d0d737472696e675f766563746f7208737472696e675b5d0f74696d655f73696e63655f6d696e740675696e7433320f75696e7436345f745f766563746f720875696e7436345b5d2f08616374697665727300000a617574686d696e74657200050a617574686f72697a6572046e616d6511617574686f72697a65645f6d696e746572046e616d6510746f6b656e5f666163746f72795f69640675696e743634087175616e746974790675696e743332046d656d6f06737472696e670d617574686d696e7465725f763100060a617574686f72697a6572046e616d6511617574686f72697a65645f6d696e746572046e616d6510746f6b656e5f666163746f72795f69640675696e743634087175616e746974790675696e743332136d6178696d756d5f756f735f7061796d656e740661737365743f046d656d6f06737472696e6715617574686f72697a65645f6d696e746572735f7630000211617574686f72697a65645f6d696e746572046e616d65087175616e746974790675696e743332046275726e0001046275726e096275726e5f77726170096275726e5f777261700003056f776e6572056e616d653f09746f6b656e5f6964731075696e7436345f745f766563746f723f046d656d6f07737472696e673f03627579000103627579086275795f77726170086275795f777261700006056275796572056e616d653f087265636569766572056e616d653f08746f6b656e5f69640775696e7436343f096d61785f70726963650661737365743f0b70726f6d6f7465725f6964056e616d653f046d656d6f07737472696e673f0c63616e63656c726573656c6c00010c63616e63656c726573656c6c1163616e63656c726573656c6c5f777261701163616e63656c726573656c6c5f77726170000208746f6b656e5f69640775696e7436343f046d656d6f07737472696e673f09636c726d696e747374000310746f6b656e5f666163746f72795f69640675696e7436340d6e6f5f6f665f656e74726965730775696e7436343f046d656d6f06737472696e67066372656174650001066372656174650b6372656174655f77726170096372656174655f76310001066372656174650e6372656174655f777261705f76310b6372656174655f777261700016046d656d6f07737472696e673f0776657273696f6e0775696e7436343f0d61737365745f6d616e61676572056e616d653f0d61737365745f63726561746f72056e616d653f1f636f6e76657273696f6e5f726174655f6f7261636c655f636f6e7472616374056e616d653f0b63686f73656e5f726174650d61737365745f766563746f723f146d696e696d756d5f726573656c6c5f70726963650661737365743f0d726573616c655f73686172657314726573616c655f73686172655f766563746f723f156d696e7461626c655f77696e646f775f73746172740f74696d655f706f696e745f7365633f136d696e7461626c655f77696e646f775f656e640f74696d655f706f696e745f7365633f1474726164696e675f77696e646f775f73746172740f74696d655f706f696e745f7365633f1274726164696e675f77696e646f775f656e640f74696d655f706f696e745f7365633f13726563616c6c5f77696e646f775f73746172741074696d655f73696e63655f6d696e743f11726563616c6c5f77696e646f775f656e641074696d655f73696e63655f6d696e743f136d61785f6d696e7461626c655f746f6b656e730775696e7433323f0b6c6f636b75705f74696d650775696e7433323f17636f6e646974696f6e6c6573735f7265636569766572730c6e616d655f766563746f723f04737461740675696e74383f096d6574615f757269730e737472696e675f766563746f723f096d6574615f686173680c636865636b73756d3235363f12617574686f72697a65645f6d696e746572731c6d696e7465725f617574686f72697a6174696f6e5f766563746f7224156163636f756e745f6d696e74696e675f6c696d69740775696e743332240e6372656174655f777261705f76310016046d656d6f06737472696e670d61737365745f6d616e61676572046e616d650d61737365745f63726561746f72046e616d65146d696e696d756d5f726573656c6c5f70726963650661737365743f0d726573616c655f73686172657314726573616c655f73686172655f766563746f723f156d696e7461626c655f77696e646f775f73746172740f74696d655f706f696e745f7365633f136d696e7461626c655f77696e646f775f656e640f74696d655f706f696e745f7365633f1474726164696e675f77696e646f775f73746172740f74696d655f706f696e745f7365633f1274726164696e675f77696e646f775f656e640f74696d655f706f696e745f7365633f13726563616c6c5f77696e646f775f73746172741074696d655f73696e63655f6d696e743f11726563616c6c5f77696e646f775f656e641074696d655f73696e63655f6d696e743f136d61785f6d696e7461626c655f746f6b656e730775696e7433323f0b6c6f636b75705f74696d650775696e7433323f17636f6e646974696f6e6c6573735f7265636569766572730c6e616d655f766563746f723f04737461740675696e74383f096d6574615f757269730e737472696e675f766563746f723f096d6574615f686173680c636865636b73756d3235363f12617574686f72697a65645f6d696e746572731c6d696e7465725f617574686f72697a6174696f6e5f766563746f723f156163636f756e745f6d696e74696e675f6c696d69740775696e7433323f157472616e736665725f77696e646f775f73746172740f74696d655f706f696e745f7365633f137472616e736665725f77696e646f775f656e640f74696d655f706f696e745f7365633f136d6178696d756d5f756f735f7061796d656e740661737365743f13676c6f62616c5f726573616c655f73686172650002087265636569766572046e616d650b62617369735f706f696e740675696e7431360b676c6f62616c736861726500020573686172650675696e743136087265636569766572056e616d653f05697373756500010569737375650a69737375655f777261701269737375655f746f6b656e5f636f6e666967000310746f6b656e5f666163746f72795f69640675696e74363406616d6f756e740675696e7433320b637573746f6d5f6461746106737472696e670869737375655f763100010569737375650d69737375655f777261705f76310a69737375655f77726170000402746f056e616d653f0d746f6b656e5f636f6e666967731a69737375655f746f6b656e5f636f6e6669675f766563746f723f046d656d6f07737472696e673f0a617574686f72697a6572066e616d653f240d69737375655f777261705f7631000502746f046e616d650d746f6b656e5f636f6e666967731969737375655f746f6b656e5f636f6e6669675f766563746f72046d656d6f06737472696e670a617574686f72697a6572056e616d653f136d6178696d756d5f756f735f7061796d656e740661737365743f096c696d69746d696e74000310746f6b656e5f666163746f72795f69640675696e743634156163636f756e745f6d696e74696e675f6c696d69740675696e743332046d656d6f06737472696e67116d6967726174655f666163746f72696573000108746f74616c5f6e6f0675696e7436340e6d6967726174655f746f6b656e730002066f776e6572730b6e616d655f766563746f7208746f74616c5f6e6f0675696e743634096d6967726174696f6e0002126163746976655f6e66745f76657273696f6e0675696e743634157461626c655f6d6967726174696f6e5f73746174730675696e743634196d696e7465725f617574686f72697a6174696f6e5f696e666f000211617574686f72697a65645f6d696e746572046e616d65087175616e746974790675696e7433320b6d696e74737461745f763000020475736572046e616d65066d696e7465640675696e743332196e6578745f746f6b656e5f666163746f72795f6e756d62657200010576616c75650675696e743634116e6578745f746f6b656e5f6e756d62657200010576616c75650675696e7436340b72616d7661756c745f76300003056f776e6572046e616d6505757361676505696e743634077061796d656e7405696e74363406726563616c6c000106726563616c6c0b726563616c6c5f777261700b726563616c6c5f777261700003056f776e6572056e616d653f09746f6b656e5f6964731075696e7436345f745f766563746f723f046d656d6f07737472696e673f0c726573616c655f73686172650002087265636569766572046e616d650b62617369735f706f696e740675696e74313609726573616c655f7630000408746f6b656e5f69640675696e743634056f776e6572046e616d650570726963650561737365741470726f6d6f7465725f62617369735f706f696e740675696e74313606726573656c6c000106726573656c6c0b726573656c6c5f777261700b726573656c6c5f7772617000050673656c6c6572056e616d653f08746f6b656e5f69640775696e7436343f0570726963650661737365743f1470726f6d6f7465725f62617369735f706f696e740775696e7431363f046d656d6f07737472696e673f0a736574636f6e72656376000310746f6b656e5f666163746f72795f69640675696e743634046d656d6f06737472696e6717636f6e646974696f6e6c6573735f7265636569766572730b6e616d655f766563746f72077365746d657461000410746f6b656e5f666163746f72795f69640675696e743634046d656d6f06737472696e67096d6574615f757269730d737472696e675f766563746f72096d6574615f686173680b636865636b73756d3235360c7365746e66746d6772666c67000009736574737461747573000310746f6b656e5f666163746f72795f69640675696e743634046d656d6f06737472696e67067374617475730575696e743810746f6b656e5f666163746f72795f763000170269640675696e7436340d61737365745f6d616e61676572046e616d650d61737365745f63726561746f72046e616d651f636f6e76657273696f6e5f726174655f6f7261636c655f636f6e7472616374046e616d650b63686f73656e5f726174650761737365745b5d146d696e696d756d5f726573656c6c5f70726963650561737365740d726573616c655f7368617265730e726573616c655f73686172655b5d156d696e7461626c655f77696e646f775f73746172740775696e7433323f136d696e7461626c655f77696e646f775f656e640775696e7433323f1474726164696e675f77696e646f775f73746172740775696e7433323f1274726164696e675f77696e646f775f656e640775696e7433323f13726563616c6c5f77696e646f775f73746172740775696e7433323f11726563616c6c5f77696e646f775f656e640775696e7433323f0b6c6f636b75705f74696d650775696e7433323f17636f6e646974696f6e6c6573735f726563656976657273066e616d655b5d04737461740575696e7438096d6574615f7572697308737472696e675b5d096d6574615f686173680b636865636b73756d323536136d61785f6d696e7461626c655f746f6b656e730775696e7433323f106d696e7465645f746f6b656e735f6e6f0675696e743332126578697374696e675f746f6b656e735f6e6f0675696e74333214617574686f72697a65645f746f6b656e735f6e6f0875696e7433323f24156163636f756e745f6d696e74696e675f6c696d69740875696e7433323f2410746f6b656e5f666163746f72795f763100170269640675696e7436340d61737365745f6d616e61676572046e616d650d61737365745f63726561746f72046e616d65146d696e696d756d5f726573656c6c5f70726963650561737365740d726573616c655f7368617265730e726573616c655f73686172655b5d156d696e7461626c655f77696e646f775f73746172740775696e7433323f136d696e7461626c655f77696e646f775f656e640775696e7433323f1474726164696e675f77696e646f775f73746172740775696e7433323f1274726164696e675f77696e646f775f656e640775696e7433323f13726563616c6c5f77696e646f775f73746172740775696e7433323f11726563616c6c5f77696e646f775f656e640775696e7433323f0b6c6f636b75705f74696d650775696e7433323f17636f6e646974696f6e6c6573735f726563656976657273066e616d655b5d04737461740575696e7438096d6574615f7572697308737472696e675b5d096d6574615f686173680b636865636b73756d323536136d61785f6d696e7461626c655f746f6b656e730775696e7433323f106d696e7465645f746f6b656e735f6e6f0675696e743332126578697374696e675f746f6b656e735f6e6f0675696e74333214617574686f72697a65645f746f6b656e735f6e6f0775696e7433323f156163636f756e745f6d696e74696e675f6c696d69740775696e7433323f157472616e736665725f77696e646f775f73746172740775696e7433323f137472616e736665725f77696e646f775f656e640775696e7433323f08746f6b656e5f763000040269640675696e74363410746f6b656e5f666163746f72795f69640675696e743634096d696e745f646174650e74696d655f706f696e745f7365630d73657269616c5f6e756d6265720675696e74333208746f6b656e5f763100050269640675696e74363410746f6b656e5f666163746f72795f69640675696e743634096d696e745f646174650e74696d655f706f696e745f7365630d73657269616c5f6e756d6265720675696e7433320b756f735f7061796d656e7405696e743634087472616e736665720001087472616e736665720d7472616e736665725f777261700d7472616e736665725f7772617000040466726f6d056e616d653f02746f056e616d653f09746f6b656e5f6964731075696e7436345f745f766563746f723f046d656d6f07737472696e673f16000000f8aaed3232086163746976657273f1022d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a20616374697661746520746865206e6577206e66742076657273696f6e0a73756d6d6172793a2027616374697661746520746865206e6577206e66742076657273696f6e20746f206d69677261746520746f270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a6f6e2d7468652d666c79206d6967726174696f6e2069732061637469766174656420616e6420636f6e74696e756f7573206d6967726174696f6e2077696c6c20626520616c6c6f7765642e20763120627573696e657373206c6f676963206265636f6d6573206163746976652e00c001793ad9b2360d617574686d696e7465725f7631fe032d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a20417574686f72697a6520616e6f74686572206163636f756e7420746f206d696e7420763120746f6b656e730a73756d6d6172793a2027417574686f72697a6520616e6f74686572206163636f756e7420746f206d696e742061206c696d69746564207175616e74697479206f6620746f6b656e73206f7220746f2064656c6567617465206d696e74696e6720746f2079657420616e6f74686572206163636f756e742e270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a7b7b617574686f72697a65727d7d20617574686f72697a6573207b7b617574686f72697a65645f6d696e7465727d7d20746f206d696e74207b7b7175616e746974797d7d206f6620746f6b656e73206f7220746f2064656c6567617465206d696e74696e67206f66207570207b7b7175616e746974797d7d20746f6b656e7320746f20736f6d65206f74686572206163636f756e742e20526561736f6e3a207b7b6d656d6f7d7d2e00c055793ad9b2360a617574686d696e746572fb032d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a20417574686f72697a6520616e6f74686572206163636f756e7420746f206d696e7420746f6b656e730a73756d6d6172793a2027417574686f72697a6520616e6f74686572206163636f756e7420746f206d696e742061206c696d69746564207175616e74697479206f6620746f6b656e73206f7220746f2064656c6567617465206d696e74696e6720746f2079657420616e6f74686572206163636f756e742e270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a7b7b617574686f72697a65727d7d20617574686f72697a6573207b7b617574686f72697a65645f6d696e7465727d7d20746f206d696e74207b7b7175616e746974797d7d206f6620746f6b656e73206f7220746f2064656c6567617465206d696e74696e67206f66207570207b7b7175616e746974797d7d20746f6b656e7320746f20736f6d65206f74686572206163636f756e742e20526561736f6e3a207b7b6d656d6f7d7d2e000000000030af3e046275726edb022d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a204275726e20546f6b656e730a73756d6d6172793a20274572617365207468652070726f7669646564206c697374206f6620746f6b656e732066726f6d206578697374656e63652e270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a75736572207b7b6f776e65727d7d2077696c6c206275726e2074686520666f6c6c6f77696e6720746f6b656e73207b7b20746f6b656e5f696473207d7d2066726f6d206578697374656e63652e20526561736f6e3a207b7b206d656d6f207d7d2e000000000000bc3e036275799f032d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a2042757920546f6b656e0a73756d6d6172793a202742757920612073696e676c6520746f6b656e206c697374656420666f7220612073616c65270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a75736572207b7b62757965727d7d2077696c6c206275792074686520746f6b656e207b7b20746f6b656e5f6964207d7d20666f722061206c697374656420707269636520616e642069662073706563696669656420612070726f6d6f746572207b7b70726f6d6f7465725f69647d7d2077696c6c20706172746963697061746520696e207468652073616c6520736861726520646973747269627574696f6e2e20526561736f6e3a207b7b206d656d6f207d7d2e10a3c2ea4685a6410c63616e63656c726573656c6cf6022d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a2043616e63656c20546f6b656e20526573656c6c0a73756d6d6172793a202743616e63656c20616e206578697374696e6720746f6b656e20726573656c6c206f7065727461696f6e270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a726573656c6c206f7065726174696f6e206f662074686520746f6b656e207b7b746f6b656e5f69647d7d2077696c6c2062652063616e63656c6c656420616e642069742077696c6c206e6f206c6f6e676572206265206c697374656420666f7220612073616c652e20526561736f6e3a207b7b206d656d6f207d7d2e0000c8384f276f4409636c726d696e747374b9032d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a20436c65616e206d696e7420737461740a73756d6d6172793a2027436c65616e20746865206578697374696e67206d696e74696e6720737461747573207461626c65206f6620746f6b656e20666163746f72792e270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a636c65616e207b7b6e6f5f6f665f656e74726965737d7d2c206f7220616c6c20656e747269657320286966207b7b6e6f5f6f665f656e74726965737d7d206973206e6f742073706563696669656429206f6620746865206578697374696e67206d696e74696e6720737461747573207461626c65206f6620746f6b656e20666163746f7279206f66206964207b7b746f6b656e5f666163746f72795f69647d7d2e20526561736f6e3a207b7b6d656d6f7d7d2e00000000a86cd44506637265617465ea022d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a2043726561746520612076657273696f6e203020546f6b656e20466163746f72790a73756d6d6172793a20274372656174652061206e657720746f6b656e20666163746f72792e612e270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a7b7b61737365745f63726561746f727d7d2077696c6c20637265617465206e657720746f6b656e20666163746f72792077686963682077696c6c206265206d616e61676564206279207b7b61737365745f6d616e616765727d7d2e20526561736f6e3a207b7b206d656d6f207d7d2e00000007a86cd445096372656174655f7631ea022d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a2043726561746520612076657273696f6e203120546f6b656e20466163746f72790a73756d6d6172793a20274372656174652061206e657720746f6b656e20666163746f72792e622e270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a7b7b61737365745f63726561746f727d7d2077696c6c20637265617465206e657720746f6b656e20666163746f72792077686963682077696c6c206265206d616e61676564206279207b7b61737365745f6d616e616765727d7d2e20526561736f6e3a207b7b206d656d6f207d7d2e00d4350d477368640b676c6f62616c736861726592032d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a2053657420676c6f62616c20726573616c652073686172650a73756d6d6172793a2027536574206f722075706461746520676c6f62616c204e465420746f6b656e20726573616c65207368617265270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a65616368204e465420746f6b656e20726573616c652077696c6c206164646974696f6e616c6c792073656e642061207368617265206f66207b7b73686172657d7d20626173697320706f696e747320746f20746865206578697374696e6720676c6f62616c207368617265206163636f756e74206f72207b7b72656365697665727d7d2028696620737065636966696564290000000000a53176056973737565b9022d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a2049737375652f4d696e7420763020546f6b656e730a73756d6d6172793a20274d696e74732061206e657720746f6b656e2e61270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a75736572207b7b746f7d7d2077696c6c20726563656976652074686520666f6c6c6f77696e6720746f6b656e73207b7b20746f6b656e5f636f6e66696773207d7d2e204d656d6f3a207b7b206d656d6f207d7d2e000000e000a531760869737375655f7631b9022d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a2049737375652f4d696e7420763120546f6b656e730a73756d6d6172793a20274d696e74732061206e657720746f6b656e2e62270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a75736572207b7b746f7d7d2077696c6c20726563656976652074686520666f6c6c6f77696e6720746f6b656e73207b7b20746f6b656e5f636f6e66696773207d7d2e204d656d6f3a207b7b206d656d6f207d7d2e0000c8d3c9eca48b096c696d69746d696e748f032d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a20536574206d696e74696e67206c696d69740a73756d6d6172793a202753657420746865206e756d626572206f66206d696e74696e67206c696d697420706572206163636f756e7420666f722074686520746f6b656e20666163746f72792e270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a736574207b7b6163636f756e745f6d696e74696e675f6c696d69747d7d20617320746865206d696e74696e67206c696d697420706572206163636f756e7420666f7220746f6b656e20666163746f7279206f66206964207b7b746f6b656e5f666163746f72795f69647d7d2e20526561736f6e3a207b7b6d656d6f7d7d2e8095bb3423b32e93116d6967726174655f666163746f72696573e8022d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a204d69677261746520666163746f726965730a73756d6d6172793a20274d69677261746520746f6b656e20666163746f7279207461626c6520656e74726965732e270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a4d696772617465207b7b746f74616c5f6e6f7d7d206f6620746f6b656e20666163746f7279207461626c6520656e74726965732c206f7220616c6c207468652072656d61696e696e6720656e7472696573206966207468657920617265206c657373207468616e207b7b746f74616c5f6e6f7d7d2e00000000e7352f930e6d6967726174655f746f6b656e73e1022d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a204d69677261746520746f6b656e730a73756d6d6172793a20274d69677261746520746f6b656e207461626c6520656e74726965732e270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a4d696772617465207b7b746f74616c5f6e6f7d7d206f66207b7b6f776e6572737d7d2720746f6b656e207461626c6520656e74726965732c206f7220616c6c207468652072656d61696e696e6720656e7472696573206966207468657920617265206c657373207468616e207b7b746f74616c5f6e6f7d7d2e00000000c46890ba06726563616c6cda022d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a20526563616c6c20546f6b656e730a73756d6d6172793a2027526563616c6c20746f6b656e732066726f6d207573657220746f20746f6b656e20666163746f7279206d616e616765722e270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a7b7b61737365745f6d616e616765727d7d2077696c6c20726563616c6c20666f6c6c6f77696e6720746f6b656e73207b7b20746f6b656e5f696473207d7d2066726f6d2075736572732e20526561736f6e3a207b7b206d656d6f207d7d2e00000000c4a8b0ba06726573656c6c87032d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a20526573656c6c20546f6b656e0a73756d6d6172793a20274c69737420612073696e676c6520746f6b656e20666f7220612073616c65270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a75736572207b7b73656c6c65727d7d2077696c6c206c6973742074686520746f6b656e207b7b20746f6b656e5f6964207d7d20666f722061207072696365206f66207b7b70726963657d7d20616e6420612070726f6d6f74657220626173697320706f696e74206f66207b7b70726f6d6f7465725f62617369735f706f696e747d7d2070657263656e742e20526561736f6e3a207b7b206d656d6f207d7d2e00c046ea4e8ab2c20a736574636f6e72656376de022d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a2053657420636f6e646974696f6e6c657373207265636569766572730a73756d6d6172793a202753657420636f6e646974696f6e6c6573732072656365697665727320666f72206120746f6b656e20666163746f72792e270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a7b7b61737365745f6d616e616765727d7d2077696c6c2073657420636f6e646974696f6e6c6573732072656365697665727320666f72206120666163746f72792e20526561736f6e3a207b7b206d656d6f207d7d2e000000c06425b3c2077365746d657461da022d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a20536574206d6574610a73756d6d6172793a2027536574206d65746164617461207572697320616e64206d65746164617461206861736820666f72206120746f6b656e20666163746f72792e270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a7b7b61737365745f6d616e616765727d7d2077696c6c20736574206d657461646174612075726920616e64206d65746164617461206861736820666f72206120666163746f72792e20526561736f6e3a207b7b206d656d6f207d7d2ec0e2ba4ce635b3c20c7365746e66746d6772666c67aa022d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a2053657420746f6b656e206d6967726174696f6e20646f6e6520666c61670a73756d6d6172793a202753657420746f6b656e206d6967726174696f6e20646f6e6520666c61672e270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a53657420746f6b656e206d6967726174696f6e20646f6e6520666c616720696e206d6967726174696f6e207461626c652e0000c03a9b8cb3c209736574737461747573a8022d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a205365742073746174650a73756d6d6172793a202753657420737461746520666f72206120746f6b656e20666163746f72792e270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a7b7b61737365745f6d616e616765727d7d2077696c6c2073657420737461746520666f72206120666163746f72792e20526561736f6e3a207b7b206d656d6f207d7d2e000000572d3ccdcd087472616e73666572dd022d2d2d0a737065635f76657273696f6e3a2022302e322e30220a7469746c653a205472616e7366657220546f6b656e730a73756d6d6172793a20275472616e73666572206d756c7469706c6520746f6b656e73206265747765656e206163636f756e7473270a69636f6e3a20687474703a2f2f3132372e302e302e312f72696361726469616e5f6173736574732f656f73696f2e636f6e7472616374732f69636f6e732f6163636f756e742e706e6723336435356132666333613563323062343536663536353766616636363662633235666664303666343833366335653832353666373431313439623062323934660a2d2d2d0a0a75736572207b7b66726f6d7d7d2077696c6c207472616e736665722074686520666f6c6c6f77696e6720746f6b656e73207b7b20746f6b656e5f696473207d7d20746f207b7b746f7d7d206163636f756e742e20526561736f6e3a207b7b206d656d6f207d7d2e0c6000be793ad9b23603693634000015617574686f72697a65645f6d696e746572735f7630000030c05f9a915903693634000010746f6b656e5f666163746f72795f7630000038c05f9a915903693634000010746f6b656e5f666163746f72795f7631a0ae69184473686403693634000013676c6f62616c5f726573616c655f7368617265000098d465739993036936340000096d6967726174696f6e008001d9649ca7930369363400000b6d696e74737461745f7630e02fcdc82c90bb9a036936340000196e6578745f746f6b656e5f666163746f72795f6e756d62657200c054906690bb9a036936340000116e6578745f746f6b656e5f6e756d626572008001396ab3a5b90369363400000b72616d7661756c745f763000000006a868b0ba03693634000009726573616c655f7630000000c080a920cd03693634000008746f6b656e5f7630000000e080a920cd03693634000008746f6b656e5f763100000000 \ No newline at end of file diff --git a/testdata/abi.json b/testdata/abi.json new file mode 100644 index 0000000..27eac6e --- /dev/null +++ b/testdata/abi.json @@ -0,0 +1,1279 @@ +{ + "version": "eosio::abi/1.1", + "types": [ + { + "new_type_name": "asset_vector", + "type": "asset[]" + }, + { + "new_type_name": "issue_token_config_vector", + "type": "issue_token_config[]" + }, + { + "new_type_name": "minter_authorization_vector", + "type": "minter_authorization_info[]" + }, + { + "new_type_name": "name_vector", + "type": "name[]" + }, + { + "new_type_name": "resale_share_vector", + "type": "resale_share[]" + }, + { + "new_type_name": "string_vector", + "type": "string[]" + }, + { + "new_type_name": "time_since_mint", + "type": "uint32" + }, + { + "new_type_name": "uint64_t_vector", + "type": "uint64[]" + } + ], + "structs": [ + { + "name": "activers", + "base": "" + }, + { + "name": "authminter", + "base": "", + "fields": [ + { + "name": "authorizer", + "type": "name" + }, + { + "name": "authorized_minter", + "type": "name" + }, + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "quantity", + "type": "uint32" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "authminter_v1", + "base": "", + "fields": [ + { + "name": "authorizer", + "type": "name" + }, + { + "name": "authorized_minter", + "type": "name" + }, + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "quantity", + "type": "uint32" + }, + { + "name": "maximum_uos_payment", + "type": "asset?" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "authorized_minters_v0", + "base": "", + "fields": [ + { + "name": "authorized_minter", + "type": "name" + }, + { + "name": "quantity", + "type": "uint32" + } + ] + }, + { + "name": "burn", + "base": "", + "fields": [ + { + "name": "burn", + "type": "burn_wrap" + } + ] + }, + { + "name": "burn_wrap", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name?" + }, + { + "name": "token_ids", + "type": "uint64_t_vector?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "buy", + "base": "", + "fields": [ + { + "name": "buy", + "type": "buy_wrap" + } + ] + }, + { + "name": "buy_wrap", + "base": "", + "fields": [ + { + "name": "buyer", + "type": "name?" + }, + { + "name": "receiver", + "type": "name?" + }, + { + "name": "token_id", + "type": "uint64?" + }, + { + "name": "max_price", + "type": "asset?" + }, + { + "name": "promoter_id", + "type": "name?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "cancelresell", + "base": "", + "fields": [ + { + "name": "cancelresell", + "type": "cancelresell_wrap" + } + ] + }, + { + "name": "cancelresell_wrap", + "base": "", + "fields": [ + { + "name": "token_id", + "type": "uint64?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "clrmintst", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "no_of_entries", + "type": "uint64?" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "create", + "base": "", + "fields": [ + { + "name": "create", + "type": "create_wrap" + } + ] + }, + { + "name": "create_v1", + "base": "", + "fields": [ + { + "name": "create", + "type": "create_wrap_v1" + } + ] + }, + { + "name": "create_wrap", + "base": "", + "fields": [ + { + "name": "memo", + "type": "string?" + }, + { + "name": "version", + "type": "uint64?" + }, + { + "name": "asset_manager", + "type": "name?" + }, + { + "name": "asset_creator", + "type": "name?" + }, + { + "name": "conversion_rate_oracle_contract", + "type": "name?" + }, + { + "name": "chosen_rate", + "type": "asset_vector?" + }, + { + "name": "minimum_resell_price", + "type": "asset?" + }, + { + "name": "resale_shares", + "type": "resale_share_vector?" + }, + { + "name": "mintable_window_start", + "type": "time_point_sec?" + }, + { + "name": "mintable_window_end", + "type": "time_point_sec?" + }, + { + "name": "trading_window_start", + "type": "time_point_sec?" + }, + { + "name": "trading_window_end", + "type": "time_point_sec?" + }, + { + "name": "recall_window_start", + "type": "time_since_mint?" + }, + { + "name": "recall_window_end", + "type": "time_since_mint?" + }, + { + "name": "max_mintable_tokens", + "type": "uint32?" + }, + { + "name": "lockup_time", + "type": "uint32?" + }, + { + "name": "conditionless_receivers", + "type": "name_vector?" + }, + { + "name": "stat", + "type": "uint8?" + }, + { + "name": "meta_uris", + "type": "string_vector?" + }, + { + "name": "meta_hash", + "type": "checksum256?" + }, + { + "name": "authorized_minters", + "type": "minter_authorization_vector$" + }, + { + "name": "account_minting_limit", + "type": "uint32$" + } + ] + }, + { + "name": "create_wrap_v1", + "base": "", + "fields": [ + { + "name": "memo", + "type": "string" + }, + { + "name": "asset_manager", + "type": "name" + }, + { + "name": "asset_creator", + "type": "name" + }, + { + "name": "minimum_resell_price", + "type": "asset?" + }, + { + "name": "resale_shares", + "type": "resale_share_vector?" + }, + { + "name": "mintable_window_start", + "type": "time_point_sec?" + }, + { + "name": "mintable_window_end", + "type": "time_point_sec?" + }, + { + "name": "trading_window_start", + "type": "time_point_sec?" + }, + { + "name": "trading_window_end", + "type": "time_point_sec?" + }, + { + "name": "recall_window_start", + "type": "time_since_mint?" + }, + { + "name": "recall_window_end", + "type": "time_since_mint?" + }, + { + "name": "max_mintable_tokens", + "type": "uint32?" + }, + { + "name": "lockup_time", + "type": "uint32?" + }, + { + "name": "conditionless_receivers", + "type": "name_vector?" + }, + { + "name": "stat", + "type": "uint8?" + }, + { + "name": "meta_uris", + "type": "string_vector?" + }, + { + "name": "meta_hash", + "type": "checksum256?" + }, + { + "name": "authorized_minters", + "type": "minter_authorization_vector?" + }, + { + "name": "account_minting_limit", + "type": "uint32?" + }, + { + "name": "transfer_window_start", + "type": "time_point_sec?" + }, + { + "name": "transfer_window_end", + "type": "time_point_sec?" + }, + { + "name": "maximum_uos_payment", + "type": "asset?" + } + ] + }, + { + "name": "global_resale_share", + "base": "", + "fields": [ + { + "name": "receiver", + "type": "name" + }, + { + "name": "basis_point", + "type": "uint16" + } + ] + }, + { + "name": "globalshare", + "base": "", + "fields": [ + { + "name": "share", + "type": "uint16" + }, + { + "name": "receiver", + "type": "name?" + } + ] + }, + { + "name": "issue", + "base": "", + "fields": [ + { + "name": "issue", + "type": "issue_wrap" + } + ] + }, + { + "name": "issue_token_config", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "amount", + "type": "uint32" + }, + { + "name": "custom_data", + "type": "string" + } + ] + }, + { + "name": "issue_v1", + "base": "", + "fields": [ + { + "name": "issue", + "type": "issue_wrap_v1" + } + ] + }, + { + "name": "issue_wrap", + "base": "", + "fields": [ + { + "name": "to", + "type": "name?" + }, + { + "name": "token_configs", + "type": "issue_token_config_vector?" + }, + { + "name": "memo", + "type": "string?" + }, + { + "name": "authorizer", + "type": "name?$" + } + ] + }, + { + "name": "issue_wrap_v1", + "base": "", + "fields": [ + { + "name": "to", + "type": "name" + }, + { + "name": "token_configs", + "type": "issue_token_config_vector" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "authorizer", + "type": "name?" + }, + { + "name": "maximum_uos_payment", + "type": "asset?" + } + ] + }, + { + "name": "limitmint", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "account_minting_limit", + "type": "uint32" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "migrate_factories", + "base": "", + "fields": [ + { + "name": "total_no", + "type": "uint64" + } + ] + }, + { + "name": "migrate_tokens", + "base": "", + "fields": [ + { + "name": "owners", + "type": "name_vector" + }, + { + "name": "total_no", + "type": "uint64" + } + ] + }, + { + "name": "migration", + "base": "", + "fields": [ + { + "name": "active_nft_version", + "type": "uint64" + }, + { + "name": "table_migration_stats", + "type": "uint64" + } + ] + }, + { + "name": "minter_authorization_info", + "base": "", + "fields": [ + { + "name": "authorized_minter", + "type": "name" + }, + { + "name": "quantity", + "type": "uint32" + } + ] + }, + { + "name": "mintstat_v0", + "base": "", + "fields": [ + { + "name": "user", + "type": "name" + }, + { + "name": "minted", + "type": "uint32" + } + ] + }, + { + "name": "next_token_factory_number", + "base": "", + "fields": [ + { + "name": "value", + "type": "uint64" + } + ] + }, + { + "name": "next_token_number", + "base": "", + "fields": [ + { + "name": "value", + "type": "uint64" + } + ] + }, + { + "name": "ramvault_v0", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "usage", + "type": "int64" + }, + { + "name": "payment", + "type": "int64" + } + ] + }, + { + "name": "recall", + "base": "", + "fields": [ + { + "name": "recall", + "type": "recall_wrap" + } + ] + }, + { + "name": "recall_wrap", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name?" + }, + { + "name": "token_ids", + "type": "uint64_t_vector?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "resale_share", + "base": "", + "fields": [ + { + "name": "receiver", + "type": "name" + }, + { + "name": "basis_point", + "type": "uint16" + } + ] + }, + { + "name": "resale_v0", + "base": "", + "fields": [ + { + "name": "token_id", + "type": "uint64" + }, + { + "name": "owner", + "type": "name" + }, + { + "name": "price", + "type": "asset" + }, + { + "name": "promoter_basis_point", + "type": "uint16" + } + ] + }, + { + "name": "resell", + "base": "", + "fields": [ + { + "name": "resell", + "type": "resell_wrap" + } + ] + }, + { + "name": "resell_wrap", + "base": "", + "fields": [ + { + "name": "seller", + "type": "name?" + }, + { + "name": "token_id", + "type": "uint64?" + }, + { + "name": "price", + "type": "asset?" + }, + { + "name": "promoter_basis_point", + "type": "uint16?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "setconrecv", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "conditionless_receivers", + "type": "name_vector" + } + ] + }, + { + "name": "setmeta", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "meta_uris", + "type": "string_vector" + }, + { + "name": "meta_hash", + "type": "checksum256" + } + ] + }, + { + "name": "setnftmgrflg", + "base": "" + }, + { + "name": "setstatus", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "status", + "type": "uint8" + } + ] + }, + { + "name": "token_factory_v0", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "asset_manager", + "type": "name" + }, + { + "name": "asset_creator", + "type": "name" + }, + { + "name": "conversion_rate_oracle_contract", + "type": "name" + }, + { + "name": "chosen_rate", + "type": "asset[]" + }, + { + "name": "minimum_resell_price", + "type": "asset" + }, + { + "name": "resale_shares", + "type": "resale_share[]" + }, + { + "name": "mintable_window_start", + "type": "uint32?" + }, + { + "name": "mintable_window_end", + "type": "uint32?" + }, + { + "name": "trading_window_start", + "type": "uint32?" + }, + { + "name": "trading_window_end", + "type": "uint32?" + }, + { + "name": "recall_window_start", + "type": "uint32?" + }, + { + "name": "recall_window_end", + "type": "uint32?" + }, + { + "name": "lockup_time", + "type": "uint32?" + }, + { + "name": "conditionless_receivers", + "type": "name[]" + }, + { + "name": "stat", + "type": "uint8" + }, + { + "name": "meta_uris", + "type": "string[]" + }, + { + "name": "meta_hash", + "type": "checksum256" + }, + { + "name": "max_mintable_tokens", + "type": "uint32?" + }, + { + "name": "minted_tokens_no", + "type": "uint32" + }, + { + "name": "existing_tokens_no", + "type": "uint32" + }, + { + "name": "authorized_tokens_no", + "type": "uint32?$" + }, + { + "name": "account_minting_limit", + "type": "uint32?$" + } + ] + }, + { + "name": "token_factory_v1", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "asset_manager", + "type": "name" + }, + { + "name": "asset_creator", + "type": "name" + }, + { + "name": "minimum_resell_price", + "type": "asset" + }, + { + "name": "resale_shares", + "type": "resale_share[]" + }, + { + "name": "mintable_window_start", + "type": "uint32?" + }, + { + "name": "mintable_window_end", + "type": "uint32?" + }, + { + "name": "trading_window_start", + "type": "uint32?" + }, + { + "name": "trading_window_end", + "type": "uint32?" + }, + { + "name": "recall_window_start", + "type": "uint32?" + }, + { + "name": "recall_window_end", + "type": "uint32?" + }, + { + "name": "lockup_time", + "type": "uint32?" + }, + { + "name": "conditionless_receivers", + "type": "name[]" + }, + { + "name": "stat", + "type": "uint8" + }, + { + "name": "meta_uris", + "type": "string[]" + }, + { + "name": "meta_hash", + "type": "checksum256" + }, + { + "name": "max_mintable_tokens", + "type": "uint32?" + }, + { + "name": "minted_tokens_no", + "type": "uint32" + }, + { + "name": "existing_tokens_no", + "type": "uint32" + }, + { + "name": "authorized_tokens_no", + "type": "uint32?" + }, + { + "name": "account_minting_limit", + "type": "uint32?" + }, + { + "name": "transfer_window_start", + "type": "uint32?" + }, + { + "name": "transfer_window_end", + "type": "uint32?" + } + ] + }, + { + "name": "token_v0", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "mint_date", + "type": "time_point_sec" + }, + { + "name": "serial_number", + "type": "uint32" + } + ] + }, + { + "name": "token_v1", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "mint_date", + "type": "time_point_sec" + }, + { + "name": "serial_number", + "type": "uint32" + }, + { + "name": "uos_payment", + "type": "int64" + } + ] + }, + { + "name": "transfer", + "base": "", + "fields": [ + { + "name": "transfer", + "type": "transfer_wrap" + } + ] + }, + { + "name": "transfer_wrap", + "base": "", + "fields": [ + { + "name": "from", + "type": "name?" + }, + { + "name": "to", + "type": "name?" + }, + { + "name": "token_ids", + "type": "uint64_t_vector?" + }, + { + "name": "memo", + "type": "string?" + } + ] + } + ], + "actions": [ + { + "name": "activers", + "type": "activers", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: activate the new nft version\nsummary: 'activate the new nft version to migrate to'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\non-the-fly migration is activated and continuous migration will be allowed. v1 business logic becomes active." + }, + { + "name": "authmint.b", + "type": "authminter_v1", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Authorize another account to mint v1 tokens\nsummary: 'Authorize another account to mint a limited quantity of tokens or to delegate minting to yet another account.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{authorizer}} authorizes {{authorized_minter}} to mint {{quantity}} of tokens or to delegate minting of up {{quantity}} tokens to some other account. Reason: {{memo}}." + }, + { + "name": "authminter", + "type": "authminter", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Authorize another account to mint tokens\nsummary: 'Authorize another account to mint a limited quantity of tokens or to delegate minting to yet another account.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{authorizer}} authorizes {{authorized_minter}} to mint {{quantity}} of tokens or to delegate minting of up {{quantity}} tokens to some other account. Reason: {{memo}}." + }, + { + "name": "burn", + "type": "burn", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Burn Tokens\nsummary: 'Erase the provided list of tokens from existence.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{owner}} will burn the following tokens {{ token_ids }} from existence. Reason: {{ memo }}." + }, + { + "name": "buy", + "type": "buy", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Buy Token\nsummary: 'Buy a single token listed for a sale'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{buyer}} will buy the token {{ token_id }} for a listed price and if specified a promoter {{promoter_id}} will participate in the sale share distribution. Reason: {{ memo }}." + }, + { + "name": "cancelresell", + "type": "cancelresell", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Cancel Token Resell\nsummary: 'Cancel an existing token resell opertaion'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nresell operation of the token {{token_id}} will be cancelled and it will no longer be listed for a sale. Reason: {{ memo }}." + }, + { + "name": "clrmintst", + "type": "clrmintst", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Clean mint stat\nsummary: 'Clean the existing minting status table of token factory.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nclean {{no_of_entries}}, or all entries (if {{no_of_entries}} is not specified) of the existing minting status table of token factory of id {{token_factory_id}}. Reason: {{memo}}." + }, + { + "name": "create", + "type": "create", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create a version 0 Token Factory\nsummary: 'Create a new token factory.a.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_creator}} will create new token factory which will be managed by {{asset_manager}}. Reason: {{ memo }}." + }, + { + "name": "create.b", + "type": "create_v1", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create a version 1 Token Factory\nsummary: 'Create a new token factory.b.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_creator}} will create new token factory which will be managed by {{asset_manager}}. Reason: {{ memo }}." + }, + { + "name": "globalshare", + "type": "globalshare", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set global resale share\nsummary: 'Set or update global NFT token resale share'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\neach NFT token resale will additionally send a share of {{share}} basis points to the existing global share account or {{receiver}} (if specified)" + }, + { + "name": "issue", + "type": "issue", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Issue/Mint v0 Tokens\nsummary: 'Mints a new token.a'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{to}} will receive the following tokens {{ token_configs }}. Memo: {{ memo }}." + }, + { + "name": "issue.b", + "type": "issue_v1", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Issue/Mint v1 Tokens\nsummary: 'Mints a new token.b'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{to}} will receive the following tokens {{ token_configs }}. Memo: {{ memo }}." + }, + { + "name": "limitmint", + "type": "limitmint", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set minting limit\nsummary: 'Set the number of minting limit per account for the token factory.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nset {{account_minting_limit}} as the minting limit per account for token factory of id {{token_factory_id}}. Reason: {{memo}}." + }, + { + "name": "mgrfactories", + "type": "migrate_factories", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Migrate factories\nsummary: 'Migrate token factory table entries.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nMigrate {{total_no}} of token factory table entries, or all the remaining entries if they are less than {{total_no}}." + }, + { + "name": "mgrnfts", + "type": "migrate_tokens", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Migrate tokens\nsummary: 'Migrate token table entries.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nMigrate {{total_no}} of {{owners}}' token table entries, or all the remaining entries if they are less than {{total_no}}." + }, + { + "name": "recall", + "type": "recall", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Recall Tokens\nsummary: 'Recall tokens from user to token factory manager.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will recall following tokens {{ token_ids }} from users. Reason: {{ memo }}." + }, + { + "name": "resell", + "type": "resell", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Resell Token\nsummary: 'List a single token for a sale'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{seller}} will list the token {{ token_id }} for a price of {{price}} and a promoter basis point of {{promoter_basis_point}} percent. Reason: {{ memo }}." + }, + { + "name": "setconrecv", + "type": "setconrecv", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set conditionless receivers\nsummary: 'Set conditionless receivers for a token factory.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will set conditionless receivers for a factory. Reason: {{ memo }}." + }, + { + "name": "setmeta", + "type": "setmeta", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set meta\nsummary: 'Set metadata uris and metadata hash for a token factory.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will set metadata uri and metadata hash for a factory. Reason: {{ memo }}." + }, + { + "name": "setnftmgrflg", + "type": "setnftmgrflg", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set token migration done flag\nsummary: 'Set token migration done flag.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nSet token migration done flag in migration table." + }, + { + "name": "setstatus", + "type": "setstatus", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set state\nsummary: 'Set state for a token factory.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will set state for a factory. Reason: {{ memo }}." + }, + { + "name": "transfer", + "type": "transfer", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Transfer Tokens\nsummary: 'Transfer multiple tokens between accounts'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{from}} will transfer the following tokens {{ token_ids }} to {{to}} account. Reason: {{ memo }}." + } + ], + "tables": [ + { + "name": "authmintrs.a", + "index_type": "i64", + "type": "authorized_minters_v0" + }, + { + "name": "factory.a", + "index_type": "i64", + "type": "token_factory_v0" + }, + { + "name": "factory.b", + "index_type": "i64", + "type": "token_factory_v1" + }, + { + "name": "global.share", + "index_type": "i64", + "type": "global_resale_share" + }, + { + "name": "migration", + "index_type": "i64", + "type": "migration" + }, + { + "name": "mintstat.a", + "index_type": "i64", + "type": "mintstat_v0" + }, + { + "name": "next.factory", + "index_type": "i64", + "type": "next_token_factory_number" + }, + { + "name": "next.token", + "index_type": "i64", + "type": "next_token_number" + }, + { + "name": "ramvault.a", + "index_type": "i64", + "type": "ramvault_v0" + }, + { + "name": "resale.a", + "index_type": "i64", + "type": "resale_v0" + }, + { + "name": "token.a", + "index_type": "i64", + "type": "token_v0" + }, + { + "name": "token.b", + "index_type": "i64", + "type": "token_v1" + } + ] +} diff --git a/testdata/block-105048059.pb.json b/testdata/block-105048059.pb.json new file mode 100644 index 0000000..4eee5e7 --- /dev/null +++ b/testdata/block-105048059.pb.json @@ -0,0 +1,546 @@ +{ + "id": "0642e7fb22a3c122448cf7db6dcd2a5b3997b7ad3da091a03566a97b67d6bc41", + "number": 105048059, + "version": 1, + "header": { + "timestamp": "2023-01-25T09:21:03Z", + "producer": "eosriobrazil", + "previous": "0642e7fa90bdd8bd6f57f0776871bd175304d77bec5fb03d53b7641e51eb98b6", + "transactionMroot": "EVckkl1c3PnT3YWA8S9VAjZ+9C7CDbIWVsLuCylmTK8=", + "actionMroot": "nRsxFWsGFgVJLWXgY2r17ryqXZaORZ/3/+nJDdC8F10=", + "scheduleVersion": 27 + }, + "producerSignature": "SIG_K1_Kas4EyiF8RcU9DpKESzgTRp1FeFuK6jpV8VKa5qTCFukF3k4bKVSQdd2M8vtgyNnVQ6ARnhaHQmh7syfVRHGiZg4ek9oTb", + "dposProposedIrreversibleBlocknum": 105048016, + "dposIrreversibleBlocknum": 105047968, + "blockrootMerkle": { + "nodeCount": 105048058, + "activeNodes": [ + "N99zVZ1sw6OpSsYymmUvf6kU5f6a9JXOrQBPmcnTS1k=", + "wMU9RYiV+wxjNU9LXkXEca5Ssp1VxngrGnQb9uyCKOI=", + "Yhu/IRKG/MGbaWYGmcP5TQNEpLyFc3Ba7Xe0Fsn5OUo=", + "ubns1k5fJlXeUEq7ZJRtsSqkU1XejP032NcgWirz55g=", + "eh8tdycQjVr7WiQBqrmVeni6k+kKbtomsaY2YNqoNDM=", + "0fVoErHH8OXX3Co/bEiIbIi8c7hZFWpidOEkHkjDnDg=", + "ZXFgRq7awi0gox39ktRApPUiC/3ZY1Wpi/thVxywakQ=", + "gcMxZXrswFmvCiILSx3u7aX860OBxMB3HrCfwrdcbfI=", + "gIhP2pYNyOnwAFxFUaxRgvFNzcWJ7Zoc1nLA5Siv8AM=", + "x+Bq9UoVR2W87o09TA7EB6dzlzUdozEnQYMoSvRcfGQ=", + "ZmeteOR9wDuJ1fI0JY1/xEW1PDUetMddJII+SxmTlUI=", + "cDF48B7JYHVhTr74ntWXcPNRbF452PUQcZP0tZjrwvY=", + "x41y8dipHU7sxJv22nREswldLo/WcpJL0agDuxqxwOw=", + "MEAvPA5vyyK5eM65JW/V8oIJ0waIK9/+crXIGSGStAg=", + "GMFq7AYVAA6d492dtbnSDkZLydrPsRDIUZ1iVolhl+I=", + "uYx3mCi/XtvUM704bE0oggtbgFlEu+CQC3/kZmimkmM=", + "eRGq8oKhJewjwqc0OOpkwZFHzqUo0o9nW5kSqHggbTE=" + ] + }, + "producerToLastProduced": [ + { + "name": "cryptolions1", + "lastBlockNumProduced": 105047992 + }, + { + "name": "eosnationftw", + "lastBlockNumProduced": 105048028 + }, + { + "name": "eosriobrazil", + "lastBlockNumProduced": 105048059 + }, + { + "name": "eosubisoft1", + "lastBlockNumProduced": 105048004 + }, + { + "name": "ivote4eosusa", + "lastBlockNumProduced": 105048040 + }, + { + "name": "uoseouldotio", + "lastBlockNumProduced": 105048052 + }, + { + "name": "uosswedenorg", + "lastBlockNumProduced": 105048016 + } + ], + "producerToLastImpliedIrb": [ + { + "name": "cryptolions1", + "lastBlockNumProduced": 105047944 + }, + { + "name": "eosnationftw", + "lastBlockNumProduced": 105047980 + }, + { + "name": "eosriobrazil", + "lastBlockNumProduced": 105048016 + }, + { + "name": "eosubisoft1", + "lastBlockNumProduced": 105047956 + }, + { + "name": "ivote4eosusa", + "lastBlockNumProduced": 105047992 + }, + { + "name": "uoseouldotio", + "lastBlockNumProduced": 105048004 + }, + { + "name": "uosswedenorg", + "lastBlockNumProduced": 105047968 + } + ], + "confirmCount": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 4, + 4, + 4 + ], + "pendingSchedule": { + "scheduleLibNum": 62666272, + "scheduleHash": "F6Mdb32w1liWWNCmH3fd6Ba86YYfbjBcfq/lmiUzfPI=", + "scheduleV2": { + "version": 27 + } + }, + "activatedProtocolFeatures": { + "protocolFeatures": [ + "DsfggBd7LAKyeNUIhhFoa0nXOZJaktm/ys1/xrdAU70=", + "GpmlnYfgbgnsWwKKnLt3SbSlrYgZAENl0C3EN5qLckE=", + "JlL1+WAGKUEJs90LveY2k/VTJK9FK3me4TeoGpBe7SU=", + "KZ3LavaSMkuJmznxbVpTCjMGKATkHwncl+nxVrRHZwc=", + "SpDADVVFTcWwWQVcohNXnG6oVpZ3EqVgF0h4hqTUzA8=", + "TnvzSNoAqUVImypoF0nrVvXeALkAAU4Tfdrjn0j2nWc=", + "T8qL2Cu9GB5xTig/g+G0XZXKWvQPuJrTl3tlPESPeMI=", + "aNyqNMBRfRlmbmszrdZzUdjF9p6ZnKHjeTG8QQopdCg=", + "hhD9I0EwDu2GzgKTqCX+51OHZNeQlH85Frjk0ANrxMQ=", + "i6Uv56OVbFzTplajF0uTHTuyq7RVeL78WfKD7NgWpAU=", + "rZ49j2UGh3Cf1o9LkLQffYJaNlsCwjpjbO+IrCrADEM=", + "4PtksQhcxVOJcBWNBaAJwk4nb7lOGgv2pSi0j7xP9SY=", + "70MRLGVDuI2yKDouB3J4wxWuLIRxmosl8lzIhWX76pk=", + "8K9W0sWkjWCkpbXJA+37fbOnNqlO1YnQt5ffM/+dPh0=" + ] + }, + "rlimitOps": [ + { + "operation": "OPERATION_UPDATE", + "state": { + "averageBlockNetUsage": { + "lastOrdinal": 105048058, + "valueEx": "32437547", + "consumed": "41" + }, + "averageBlockCpuUsage": { + "lastOrdinal": 105048058, + "valueEx": "10246360", + "consumed": "12" + }, + "pendingNetUsage": "272", + "pendingCpuUsage": "101", + "totalNetWeight": "601000000", + "totalCpuWeight": "601000000", + "totalRamBytes": "4542727343", + "virtualNetLimit": "1048576", + "virtualCpuLimit": "400000" + } + }, + { + "operation": "OPERATION_UPDATE", + "state": { + "averageBlockNetUsage": { + "lastOrdinal": 105048059, + "valueEx": "34433901", + "consumed": "305" + }, + "averageBlockCpuUsage": { + "lastOrdinal": 105048059, + "valueEx": "11002640", + "consumed": "112" + }, + "totalNetWeight": "601000000", + "totalCpuWeight": "601000000", + "totalRamBytes": "4542727343", + "virtualNetLimit": "1048576", + "virtualCpuLimit": "400000" + } + } + ], + "filteredTransactions": [ + { + "id": "49cec0e064881247b9175d8d9fe3fc0dfdfa5a8241304ff874fef56596749e72", + "status": "TRANSACTIONSTATUS_EXECUTED", + "cpuUsageMicroSeconds": 100, + "netUsageWords": 33, + "packedTransaction": { + "signatures": [ + "SIG_K1_KcF5YJEcr1yc3eKxqwpvP8XWBUNZ2m7c3peDHFY1EHET9upzyHbeWN2LU9v5Zk6aArds8GUuCXjUXJNNqJ6o2AszkYDGWh" + ], + "packedTransaction": "m/TQY6DnY4rawAAAAAABAKaCNAPqMFUAAABXLTzNzQFgETFmGCOMCQAAAACo7TIyLmARMWYYI4wJ0A8xZhgjjAkAypo7AAAAAAZURVNUVVNEDXRlc3QgdHJhbnNmZXIA" + } + } + ], + "unfilteredTransactionCount": 1, + "filteredTransactionCount": 1, + "filteredTransactionTraces": [ + { + "id": "49cec0e064881247b9175d8d9fe3fc0dfdfa5a8241304ff874fef56596749e72", + "blockNum": "105048059", + "index": "1", + "blockTime": "2023-01-25T09:21:03Z", + "producerBlockId": "0642e7fb22a3c122448cf7db6dcd2a5b3997b7ad3da091a03566a97b67d6bc41", + "receipt": { + "status": "TRANSACTIONSTATUS_EXECUTED", + "cpuUsageMicroSeconds": 100, + "netUsageWords": 33 + }, + "elapsed": "497", + "netUsage": "264", + "actionTraces": [ + { + "receiver": "eosio.token", + "receipt": { + "receiver": "eosio.token", + "digest": "a3ad0ed4f7de204e1fe200a613d8f5b8582da70ca237e65841c57840634365f8", + "globalSequence": "288770331", + "authSequence": [ + { + "accountName": "1aa2aa3aa4cq", + "sequence": "7" + } + ], + "recvSequence": "239943", + "codeSequence": "3", + "abiSequence": "2" + }, + "action": { + "account": "eosio.token", + "name": "transfer", + "authorization": [ + { + "actor": "1aa2aa3aa4cq", + "permission": "active" + } + ], + "jsonData": "{\"from\":\"1aa2aa3aa4cq\",\"to\":\"1aa2aa3aa4bx\",\"quantity\":\"1000.000000 TESTUSD\",\"memo\":\"test transfer\"}", + "rawData": "YBExZhgjjAnQDzFmGCOMCQDKmjsAAAAABlRFU1RVU0QNdGVzdCB0cmFuc2Zlcg==" + }, + "elapsed": "188", + "transactionId": "49cec0e064881247b9175d8d9fe3fc0dfdfa5a8241304ff874fef56596749e72", + "blockNum": "105048059", + "producerBlockId": "0642e7fb22a3c122448cf7db6dcd2a5b3997b7ad3da091a03566a97b67d6bc41", + "blockTime": "2023-01-25T09:21:03Z", + "actionOrdinal": 1, + "filteringMatched": true + }, + { + "receiver": "1aa2aa3aa4cq", + "receipt": { + "receiver": "1aa2aa3aa4cq", + "digest": "a3ad0ed4f7de204e1fe200a613d8f5b8582da70ca237e65841c57840634365f8", + "globalSequence": "288770332", + "authSequence": [ + { + "accountName": "1aa2aa3aa4cq", + "sequence": "8" + } + ], + "recvSequence": "3", + "codeSequence": "3", + "abiSequence": "2" + }, + "action": { + "account": "eosio.token", + "name": "transfer", + "authorization": [ + { + "actor": "1aa2aa3aa4cq", + "permission": "active" + } + ], + "jsonData": "{\"from\":\"1aa2aa3aa4cq\",\"to\":\"1aa2aa3aa4bx\",\"quantity\":\"1000.000000 TESTUSD\",\"memo\":\"test transfer\"}", + "rawData": "YBExZhgjjAnQDzFmGCOMCQDKmjsAAAAABlRFU1RVU0QNdGVzdCB0cmFuc2Zlcg==" + }, + "elapsed": "3", + "transactionId": "49cec0e064881247b9175d8d9fe3fc0dfdfa5a8241304ff874fef56596749e72", + "blockNum": "105048059", + "producerBlockId": "0642e7fb22a3c122448cf7db6dcd2a5b3997b7ad3da091a03566a97b67d6bc41", + "blockTime": "2023-01-25T09:21:03Z", + "actionOrdinal": 2, + "creatorActionOrdinal": 1, + "closestUnnotifiedAncestorActionOrdinal": 1, + "executionIndex": 1, + "filteringMatched": true + }, + { + "receiver": "1aa2aa3aa4bx", + "receipt": { + "receiver": "1aa2aa3aa4bx", + "digest": "a3ad0ed4f7de204e1fe200a613d8f5b8582da70ca237e65841c57840634365f8", + "globalSequence": "288770333", + "authSequence": [ + { + "accountName": "1aa2aa3aa4cq", + "sequence": "9" + } + ], + "recvSequence": "8", + "codeSequence": "3", + "abiSequence": "2" + }, + "action": { + "account": "eosio.token", + "name": "transfer", + "authorization": [ + { + "actor": "1aa2aa3aa4cq", + "permission": "active" + } + ], + "jsonData": "{\"from\":\"1aa2aa3aa4cq\",\"to\":\"1aa2aa3aa4bx\",\"quantity\":\"1000.000000 TESTUSD\",\"memo\":\"test transfer\"}", + "rawData": "YBExZhgjjAnQDzFmGCOMCQDKmjsAAAAABlRFU1RVU0QNdGVzdCB0cmFuc2Zlcg==" + }, + "elapsed": "225", + "transactionId": "49cec0e064881247b9175d8d9fe3fc0dfdfa5a8241304ff874fef56596749e72", + "blockNum": "105048059", + "producerBlockId": "0642e7fb22a3c122448cf7db6dcd2a5b3997b7ad3da091a03566a97b67d6bc41", + "blockTime": "2023-01-25T09:21:03Z", + "actionOrdinal": 3, + "creatorActionOrdinal": 1, + "closestUnnotifiedAncestorActionOrdinal": 1, + "executionIndex": 2, + "filteringMatched": true + } + ], + "dbOps": [ + { + "operation": "OPERATION_UPDATE", + "code": "eosio.token", + "scope": "1aa2aa3aa4cq", + "tableName": "accounts", + "primaryKey": ".125apeoeh2p4", + "oldPayer": "eosio.token", + "newPayer": "eosio.token", + "oldData": "AMqaOwAAAAAGVEVTVFVTRA==", + "newData": "AAAAAAAAAAAGVEVTVFVTRA==" + }, + { + "operation": "OPERATION_UPDATE", + "code": "eosio.token", + "scope": "1aa2aa3aa4bx", + "tableName": "accounts", + "primaryKey": ".125apeoeh2p4", + "oldPayer": "eosio.token", + "newPayer": "eosio.token", + "oldData": "AMqaOwAAAAAGVEVTVFVTRA==", + "newData": "AJQ1dwAAAAAGVEVTVFVTRA==" + }, + { + "operation": "OPERATION_UPDATE", + "actionIndex": 2, + "code": "1aa2aa3aa4bx", + "scope": "1aa2aa3aa4cq", + "tableName": "evodexacnts", + "oldPayer": "1aa2aa3aa4bx", + "newPayer": "1aa2aa3aa4bx", + "oldData": "AAAAAAAAAAAGVEVTVFVTRACmgjQD6jBVAAAAAAAAAAA=", + "newData": "AMqaOwAAAAAGVEVTVFVTRACmgjQD6jBVAAAAAAAAAAA=" + } + ], + "rlimitOps": [ + { + "operation": "OPERATION_UPDATE", + "accountUsage": { + "owner": "1aa2aa3aa4cq", + "netUsage": { + "lastOrdinal": 1455907326, + "valueEx": "2943", + "consumed": "265" + }, + "cpuUsage": { + "lastOrdinal": 1455907326, + "valueEx": "1114", + "consumed": "101" + } + } + } + ], + "creationTree": [ + { + "creatorActionIndex": -1 + }, + { + "executionActionIndex": 1 + }, + { + "executionActionIndex": 2 + } + ] + } + ], + "unfilteredTransactionTraceCount": 2, + "filteredTransactionTraceCount": 1, + "unfilteredExecutedInputActionCount": 2, + "filteredExecutedInputActionCount": 1, + "unfilteredExecutedTotalActionCount": 4, + "filteredExecutedTotalActionCount": 3, + "validBlockSigningAuthorityV2": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS7nq54FcLXtDtY8GHqknYk5NqZPZNBVY3wCJQeRrzVzVjyXsEM6", + "weight": 1 + } + ] + } + }, + "activeScheduleV2": { + "version": 27, + "producers": [ + { + "accountName": "eosriobrazil", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS7nq54FcLXtDtY8GHqknYk5NqZPZNBVY3wCJQeRrzVzVjyXsEM6", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "cryptolions1", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS8LioNSB3ugUG7LweRBB2BR8AMJUEFGicXMs9msHByd9vMmxEKe", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "eosubisoft1", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5FgTg6xtmMaonVo3uo4S8oLTWTEUnSagAMQCvUkczu9e5hXaWp", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "uosswedenorg", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS6j7tqvStvcykc1ysDuNY1J8PqyARpGqWrxbVjiuNBQL8uL7r4A", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "eosnationftw", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5uMMtMvBp4pPEBiUsfdBR9LVnJnzUonGFvQtwnh1eoXzZZKN8U", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "ivote4eosusa", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5gKJLRWxMgLWDsvKu7YaTweoQpqin5SSzJ4mDuaXnQ6tZb1F31", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "uoseouldotio", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS7Tauu774Uz1bisTGUC37NsS6mxYoNaW3QKYF3oD1bUUJdCGxKf", + "weight": 1 + } + ] + } + } + } + ] + }, + "filteringApplied": true, + "filteringIncludeFilterExpr": "account==\"eosio.token\" || (action==\"setabi\" \u0026\u0026 account==\"eosio\")" +} \ No newline at end of file diff --git a/testdata/block-135283216.pb.json b/testdata/block-135283216.pb.json new file mode 100644 index 0000000..0816897 --- /dev/null +++ b/testdata/block-135283216.pb.json @@ -0,0 +1 @@ +{"id":"081042103ef80abcb741f7b70f39ab88399b99c96a987b4a092bdd27e7cc4c69","number":135283216,"version":1,"header":{"timestamp":"2024-10-29T07:49:41Z","producer":"produceracc2","previous":"0810420fc44344ac9fb599837d5be557c81f855d8c4549197e87bf80e9dde30e","transactionMroot":"M6BZkL8oQ5etI8Ne9p6hYbfsDeDHEcQ4Qn+ma56Oasc=","actionMroot":"Hw6+AoI30ye+3AjBCOxZikSPzKTK0wOdee7HS/+HsJk=","scheduleVersion":19},"producerSignature":"SIG_K1_Jw9HLkphKha3yzLeY5tbJKCpCSbduHuZaqftfmerWFT7hc9erSMEs1pizgi1ReYV9ud38fC9zXz7AaL8hJ3S8Lz1XXAuXy","dposProposedIrreversibleBlocknum":135283193,"dposIrreversibleBlocknum":135283169,"blockrootMerkle":{"nodeCount":135283215,"activeNodes":["CBBCD8RDRKyftZmDfVvlV8gfhV2MRUkZfoe/gOnd4w4=","8A/ahc4mO9SZguaFL6ZvkMiJErobAhmHg9GbjdQVqME=","NAU6J8CuYLuHuVcDRuTlizTX7EGSanQ3Kg8GyxRpggY=","nInEiscwqTyRSzQ8iGHmRnBX4XQQd5Q3kB7p3Q3aBIk=","a4+JddE+UEOHh9o9ml9gVHTpm18KU4GqS8qQyo45ABc=","arOGWZkgs3H+iTn5eFL6Txs3m8Iv1OZSOHWklJ3+xl0=","7jMfiLlP6pM/CyiO1nAEgl+oZ/4IVQE2bl3/tf/gnk8=","ORlzVhWsPiPpIdHFAyRmiaxj3ocLaBB/jn0sPG2HW8s=","B5mbnbQJONY1geopHLbUWoEBMNOkxykaj4MpIq7/IU4="]},"producerToLastProduced":[{"name":"produceracc1","lastBlockNumProduced":135283205},{"name":"produceracc2","lastBlockNumProduced":135283216},{"name":"produceracc3","lastBlockNumProduced":135283193}],"producerToLastImpliedIrb":[{"name":"produceracc1","lastBlockNumProduced":135283181},{"name":"produceracc2","lastBlockNumProduced":135283193},{"name":"produceracc3","lastBlockNumProduced":135283169}],"confirmCount":[1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2],"pendingSchedule":{"scheduleLibNum":112269463,"scheduleHash":"LvGZHsSbebmVOwvxueJ5qN1qe3WB6TrkWOkj8edLHsc=","scheduleV2":{"version":19}},"activatedProtocolFeatures":{"protocolFeatures":["DsfggBd7LAKyeNUIhhFoa0nXOZJaktm/ys1/xrdAU70=","D+GFuIMhUYZYxU8h6R14fymGnFvA3T/HgA95iDwUAw0=","GltEdlcoFG1xuRadzlKjHV+sIUO2GwCLfKkyj92LrEU=","GpmlnYfgbgnsWwKKnLt3SbSlrYgZAENl0C3EN5qLckE=","JlL1+WAGKUEJs90LveY2k/VTJK9FK3me4TeoGpBe7SU=","KZ3LavaSMkuJmznxbVpTCjMGKATkHwncl+nxVrRHZwc=","SpDADVVFTcWwWQVcohNXnG6oVpZ3EqVgF0h4hqTUzA8=","TnvzSNoAqUVImypoF0nrVvXeALkAAU4Tfdrjn0j2nWc=","T8qL2Cu9GB5xTig/g+G0XZXKWvQPuJrTl3tlPESPeMI=","aNyqNMBRfRlmbmszrdZzUdjF9p6ZnKHjeTG8QQopdCg=","i6Uv56OVbFzTplajF0uTHTuyq7RVeL78WfKD7NgWpAU=","rZ49j2UGh3Cf1o9LkLQffYJaNlsCwjpjbO+IrCrADEM=","rqJFf5Ud6QoTLRowexOsEnXI/CSQ0nQ53UVxU9wDr48=","sLrd2WfnkQuQVpYiShE0yoMsUcth2RpqIiZSqCcVxj8=","tUHVRFIr5UqAt+N9TvxkCLdTpxQ7oQ8XDlYCpPq4wbo=","tnweAOC5Pppk0GviuW23tvSufzPvZ1bV8sL+taeCqEM=","y3nUSfgZS/a0A+QP2jGaHi+dKyEI97bLmBZi5jk+1rk=","4PtksQhcxVOJcBWNBaAJwk4nb7lOGgv2pSi0j7xP9SY=","70MRLGVDuI2yKDouB3J4wxWuLIRxmosl8lzIhWX76pk=","8K9W0sWkjWCkpbXJA+37fbOnNqlO1YnQt5ffM/+dPh0=","/Si5iGxEafNAYqQZIy9NW4AH15C4HMxtZ1/slB59gLs="]},"rlimitOps":[{"operation":"OPERATION_UPDATE","state":{"averageBlockNetUsage":{"lastOrdinal":135283215,"valueEx":"144820103","consumed":"177"},"averageBlockCpuUsage":{"lastOrdinal":135283215,"valueEx":"52984979","consumed":"63"},"pendingNetUsage":"1312","pendingCpuUsage":"498","totalNetWeight":"11510000000","totalCpuWeight":"11510000000","totalRamBytes":"6770088669","virtualNetLimit":"1048576","virtualCpuLimit":"400000"}},{"operation":"OPERATION_UPDATE","state":{"averageBlockNetUsage":{"lastOrdinal":135283216,"valueEx":"154546602","consumed":"1456"},"averageBlockCpuUsage":{"lastOrdinal":135283216,"valueEx":"56693437","consumed":"551"},"totalNetWeight":"11510000000","totalCpuWeight":"11510000000","totalRamBytes":"6770088669","virtualNetLimit":"1048576","virtualCpuLimit":"400000"}}],"filteredTransactions":[{"id":"c0a68b41255e59aeee5664ba531cb2b8071bce9935c507683b493eb730ec3071","status":"TRANSACTIONSTATUS_EXECUTED","cpuUsageMicroSeconds":488,"netUsageWords":160,"packedTransaction":{"signatures":["SIG_K1_K1jT6nqFAnwYftjxbgDJVjDPzNg166rNCcBWNZUz3TrtE4bjagThGjFy8gHusrs2HHfUCQR1mzJGAcT62ms2duEFnf78X4"],"packedTransaction":"spMgZ+FBv9KBLgAAAAABkBfIawLqMFUAADAAewVTMgEAQqW3AnNz1AAAAACo7TIyWYEDAAAAAAAAAwRHb2xkBnVpbnQzMgcBAEKltwJzc9QBBgAAAAAGTHVtYmVyBnVpbnQzMgcBAEKltwJzc9QABlJhdGluZwdmbG9hdDMyBwEAQqW3AnNz1AAAAA=="}}],"unfilteredTransactionCount":1,"filteredTransactionCount":1,"filteredTransactionTraces":[{"id":"c0a68b41255e59aeee5664ba531cb2b8071bce9935c507683b493eb730ec3071","blockNum":"135283216","index":"1","blockTime":"2024-10-29T07:49:41Z","producerBlockId":"081042103ef80abcb741f7b70f39ab88399b99c96a987b4a092bdd27e7cc4c69","receipt":{"status":"TRANSACTIONSTATUS_EXECUTED","cpuUsageMicroSeconds":488,"netUsageWords":160},"elapsed":"520","netUsage":"1280","actionTraces":[{"receiver":"eosio.nft.ft","receipt":{"receiver":"eosio.nft.ft","digest":"541a3ce30fd0c07bfe5e359e1e725f8434b1999bcf2c0325ccdf3ed5fbb42b6f","globalSequence":"140211572","authSequence":[{"accountName":"ultra.prop1","sequence":"258"}],"recvSequence":"8361","codeSequence":"16","abiSequence":"11"},"action":{"account":"eosio.nft.ft","name":"addkeys.a","authorization":[{"actor":"ultra.prop1","permission":"active"}],"jsonData":"{\"factory_id\":897,\"key_defs\":[{\"default_value\":[\"uint32\",0],\"edit_rights\":7,\"editors\":[\"ultra.prop1\"],\"name\":\"Gold\",\"type\":\"uint32\"},{\"default_value\":null,\"edit_rights\":7,\"editors\":[\"ultra.prop1\"],\"name\":\"Lumber\",\"type\":\"uint32\"},{\"default_value\":null,\"edit_rights\":7,\"editors\":[\"ultra.prop1\"],\"name\":\"Rating\",\"type\":\"float32\"}],\"memo\":\"\"}","rawData":"gQMAAAAAAAADBEdvbGQGdWludDMyBwEAQqW3AnNz1AEGAAAAAAZMdW1iZXIGdWludDMyBwEAQqW3AnNz1AAGUmF0aW5nB2Zsb2F0MzIHAQBCpbcCc3PUAAA="},"elapsed":"273","transactionId":"c0a68b41255e59aeee5664ba531cb2b8071bce9935c507683b493eb730ec3071","blockNum":"135283216","producerBlockId":"081042103ef80abcb741f7b70f39ab88399b99c96a987b4a092bdd27e7cc4c69","blockTime":"2024-10-29T07:49:41Z","accountRamDeltas":[{"account":"eosio.nftram","delta":"78"}],"actionOrdinal":1,"filteringMatched":true},{"receiver":"eosio.token","receipt":{"receiver":"eosio.token","digest":"f786dc4094841f4547b22d4b94c5bee02165da9399ff0d3cb552fb2f7975297e","globalSequence":"140211573","authSequence":[{"accountName":"ultra.prop1","sequence":"259"}],"recvSequence":"14960","codeSequence":"3","abiSequence":"2"},"action":{"account":"eosio.token","name":"transfer","authorization":[{"actor":"ultra.prop1","permission":"active"}],"jsonData":"{\"from\":\"ultra.prop1\",\"to\":\"eosio.pool\",\"quantity\":\"0.49441259 UOS\",\"memo\":\"new key def ram payment for factory: 897\"}","rawData":"AEKltwJzc9QAQKS0AuowVetp8gIAAAAACFVPUwAAAAAobmV3IGtleSBkZWYgcmFtIHBheW1lbnQgZm9yIGZhY3Rvcnk6IDg5Nw=="},"elapsed":"97","transactionId":"c0a68b41255e59aeee5664ba531cb2b8071bce9935c507683b493eb730ec3071","blockNum":"135283216","producerBlockId":"081042103ef80abcb741f7b70f39ab88399b99c96a987b4a092bdd27e7cc4c69","blockTime":"2024-10-29T07:49:41Z","actionOrdinal":2,"creatorActionOrdinal":1,"closestUnnotifiedAncestorActionOrdinal":1,"executionIndex":1},{"receiver":"ultra.prop1","receipt":{"receiver":"ultra.prop1","digest":"f786dc4094841f4547b22d4b94c5bee02165da9399ff0d3cb552fb2f7975297e","globalSequence":"140211574","authSequence":[{"accountName":"ultra.prop1","sequence":"260"}],"recvSequence":"419","codeSequence":"3","abiSequence":"2"},"action":{"account":"eosio.token","name":"transfer","authorization":[{"actor":"ultra.prop1","permission":"active"}],"jsonData":"{\"from\":\"ultra.prop1\",\"to\":\"eosio.pool\",\"quantity\":\"0.49441259 UOS\",\"memo\":\"new key def ram payment for factory: 897\"}","rawData":"AEKltwJzc9QAQKS0AuowVetp8gIAAAAACFVPUwAAAAAobmV3IGtleSBkZWYgcmFtIHBheW1lbnQgZm9yIGZhY3Rvcnk6IDg5Nw=="},"elapsed":"2","transactionId":"c0a68b41255e59aeee5664ba531cb2b8071bce9935c507683b493eb730ec3071","blockNum":"135283216","producerBlockId":"081042103ef80abcb741f7b70f39ab88399b99c96a987b4a092bdd27e7cc4c69","blockTime":"2024-10-29T07:49:41Z","actionOrdinal":3,"creatorActionOrdinal":2,"closestUnnotifiedAncestorActionOrdinal":2,"executionIndex":2},{"receiver":"eosio.pool","receipt":{"receiver":"eosio.pool","digest":"f786dc4094841f4547b22d4b94c5bee02165da9399ff0d3cb552fb2f7975297e","globalSequence":"140211575","authSequence":[{"accountName":"ultra.prop1","sequence":"261"}],"recvSequence":"184","codeSequence":"3","abiSequence":"2"},"action":{"account":"eosio.token","name":"transfer","authorization":[{"actor":"ultra.prop1","permission":"active"}],"jsonData":"{\"from\":\"ultra.prop1\",\"to\":\"eosio.pool\",\"quantity\":\"0.49441259 UOS\",\"memo\":\"new key def ram payment for factory: 897\"}","rawData":"AEKltwJzc9QAQKS0AuowVetp8gIAAAAACFVPUwAAAAAobmV3IGtleSBkZWYgcmFtIHBheW1lbnQgZm9yIGZhY3Rvcnk6IDg5Nw=="},"elapsed":"1","transactionId":"c0a68b41255e59aeee5664ba531cb2b8071bce9935c507683b493eb730ec3071","blockNum":"135283216","producerBlockId":"081042103ef80abcb741f7b70f39ab88399b99c96a987b4a092bdd27e7cc4c69","blockTime":"2024-10-29T07:49:41Z","actionOrdinal":4,"creatorActionOrdinal":2,"closestUnnotifiedAncestorActionOrdinal":2,"executionIndex":3}],"dbOps":[{"operation":"OPERATION_UPDATE","code":"eosio.nft.ft","scope":"eosio.nft.ft","tableName":"factory.b","primaryKey":"..........1s1","oldPayer":"eosio.nftram","newPayer":"eosio.nftram","oldData":"gQMAAAAAAAAAQqW3AnNz1ABCpbcCc3PUAAAAAAAAAAAIVU9TAAAAAAAAAAAAAAAAAAAEdGVzdNV2j44qexqKl3TftTjgoZKNDZrF8IvXgcIUWbQwjcUjAAAAAAAAAAAAAAAAAAV0ZXN0MgHVdo+OKnsaipd037U44KGSjQ2axfCL14HCFFm0MI3FIwA=","newData":"gQMAAAAAAAAAQqW3AnNz1ABCpbcCc3PUAAAAAAAAAAAIVU9TAAAAAAAAAAAAAAAAAAAEdGVzdNV2j44qexqKl3TftTjgoZKNDZrF8IvXgcIUWbQwjcUjAAAAAAAAAAAAAAAAAAV0ZXN0MgHVdo+OKnsaipd037U44KGSjQ2axfCL14HCFFm0MI3FIwABAwRHb2xkBgcBAEKltwJzc9QBBgAAAAAGTHVtYmVyBgcBAEKltwJzc9QABlJhdGluZwgHAQBCpbcCc3PUAAsBAAAAAAAAFQAAAAAAAAA="},{"operation":"OPERATION_UPDATE","actionIndex":1,"code":"eosio.token","scope":"ultra.prop1","tableName":"accounts","primaryKey":"........ehbp5","oldPayer":"eosio","newPayer":"eosio","oldData":"xa/dgBYAAAAIVU9TAAAAAA==","newData":"2kXrfRYAAAAIVU9TAAAAAA=="},{"operation":"OPERATION_UPDATE","actionIndex":1,"code":"eosio.token","scope":"eosio.pool","tableName":"accounts","primaryKey":"........ehbp5","oldPayer":"eosio","newPayer":"eosio","oldData":"jhkDQBMAAAAIVU9TAAAAAA==","newData":"eYP1QhMAAAAIVU9TAAAAAA=="}],"ramOps":[{"operation":"OPERATION_PRIMARY_INDEX_UPDATE","payer":"eosio.nftram","delta":"78","usage":"1828609","namespace":"NAMESPACE_TABLE_ROW","uniqueKey":"eosio.nft.ft:eosio.nft.ft:factory.b:..........1s1","action":"ACTION_UPDATE"}],"rlimitOps":[{"operation":"OPERATION_UPDATE","accountUsage":{"owner":"ultra.prop1","netUsage":{"lastOrdinal":1567006762,"valueEx":"2714142","consumed":"1283"},"cpuUsage":{"lastOrdinal":1567006762,"valueEx":"1035343","consumed":"490"},"ramUsage":"1406"}}],"creationTree":[{"creatorActionIndex":-1},{"executionActionIndex":1},{"creatorActionIndex":1,"executionActionIndex":2},{"creatorActionIndex":1,"executionActionIndex":3}]}],"unfilteredTransactionTraceCount":2,"filteredTransactionTraceCount":1,"unfilteredExecutedInputActionCount":2,"filteredExecutedInputActionCount":1,"unfilteredExecutedTotalActionCount":5,"filteredExecutedTotalActionCount":1,"validBlockSigningAuthorityV2":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS8dajNwmCPVfr2cNzyWZDwucn5CCPV7nRCYoBB2XAcgCEBWRctX","weight":1}]}},"activeScheduleV2":{"version":19,"producers":[{"accountName":"produceracc1","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS8dajNwmCPVfr2cNzyWZDwucn5CCPV7nRCYoBB2XAcgCEBWRctX","weight":1}]}}},{"accountName":"produceracc2","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS8dajNwmCPVfr2cNzyWZDwucn5CCPV7nRCYoBB2XAcgCEBWRctX","weight":1}]}}},{"accountName":"produceracc3","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS8dajNwmCPVfr2cNzyWZDwucn5CCPV7nRCYoBB2XAcgCEBWRctX","weight":1}]}}}]},"filteringApplied":true,"filteringIncludeFilterExpr":"(account==\"eosio.nft.ft\" \u0026\u0026 receiver==\"eosio.nft.ft\") || (action==\"setabi\" \u0026\u0026 account==\"eosio\" \u0026\u0026 data.account==\"eosio.nft.ft\")"} \ No newline at end of file diff --git a/testdata/block-135283642.pb.json b/testdata/block-135283642.pb.json new file mode 100644 index 0000000..807b064 --- /dev/null +++ b/testdata/block-135283642.pb.json @@ -0,0 +1 @@ +{"id":"081043bab79398dcd4d937a7e43200a7f5e2b14297fb57bb74624b3517b969e6","number":135283642,"version":1,"header":{"timestamp":"2024-10-29T07:53:14Z","producer":"produceracc2","previous":"081043b9cd6c028ddcfe529428c8ff77f06a43dc7c10040f59d2eeadfda7c136","transactionMroot":"el0q2XNjfGM8FJozHVzNkvthkqnuUt/rEM/s1avCWSs=","actionMroot":"VegSaavtNs2lqEKBFtOVLXW3yAftAwF8LcUItUBPTPw=","scheduleVersion":19},"producerSignature":"SIG_K1_KcsyE5Zyzc6jp2vpCndzEzXYP7V8GqfHUu7yR7CpUDu3UoAFjVayhHdR5Y4iisGAnBT4cM2fhZB53ZgWUNmLULtkUiSUBy","dposProposedIrreversibleBlocknum":135283625,"dposIrreversibleBlocknum":135283601,"blockrootMerkle":{"nodeCount":135283641,"activeNodes":["CBBDuc1sAo3c/lKUKMj/d/BqQ9x8EAQPWdLurf2nwTY=","YyxtX6AO+X7j8FrpSkvg9jcMnAkUvV0FvFz8Jnac7uo=","SO9mJTfUhL4jzRSpnjEcrcVvH36enBlt39h0QoXMPC4=","6HKLh7nPPMfuVgsw+ZU3dKUAWRthOr9vpZyWLC3gQss=","nsP9Jx0XXueHK4V6xUW3eMtaqgCSWqYIG9wkJ6GYc/A=","D29ZmQekp9K0BvW/DclgIXXMf426YHiPNwAIEM5O2FU=","a4+JddE+UEOHh9o9ml9gVHTpm18KU4GqS8qQyo45ABc=","arOGWZkgs3H+iTn5eFL6Txs3m8Iv1OZSOHWklJ3+xl0=","7jMfiLlP6pM/CyiO1nAEgl+oZ/4IVQE2bl3/tf/gnk8=","ORlzVhWsPiPpIdHFAyRmiaxj3ocLaBB/jn0sPG2HW8s=","7kRLna3K737q/lTMC7tSugtbBUWogRf59AzcJRm9ouE="]},"producerToLastProduced":[{"name":"produceracc1","lastBlockNumProduced":135283637},{"name":"produceracc2","lastBlockNumProduced":135283642},{"name":"produceracc3","lastBlockNumProduced":135283625}],"producerToLastImpliedIrb":[{"name":"produceracc1","lastBlockNumProduced":135283613},{"name":"produceracc2","lastBlockNumProduced":135283625},{"name":"produceracc3","lastBlockNumProduced":135283601}],"confirmCount":[1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2],"pendingSchedule":{"scheduleLibNum":112269463,"scheduleHash":"LvGZHsSbebmVOwvxueJ5qN1qe3WB6TrkWOkj8edLHsc=","scheduleV2":{"version":19}},"activatedProtocolFeatures":{"protocolFeatures":["DsfggBd7LAKyeNUIhhFoa0nXOZJaktm/ys1/xrdAU70=","D+GFuIMhUYZYxU8h6R14fymGnFvA3T/HgA95iDwUAw0=","GltEdlcoFG1xuRadzlKjHV+sIUO2GwCLfKkyj92LrEU=","GpmlnYfgbgnsWwKKnLt3SbSlrYgZAENl0C3EN5qLckE=","JlL1+WAGKUEJs90LveY2k/VTJK9FK3me4TeoGpBe7SU=","KZ3LavaSMkuJmznxbVpTCjMGKATkHwncl+nxVrRHZwc=","SpDADVVFTcWwWQVcohNXnG6oVpZ3EqVgF0h4hqTUzA8=","TnvzSNoAqUVImypoF0nrVvXeALkAAU4Tfdrjn0j2nWc=","T8qL2Cu9GB5xTig/g+G0XZXKWvQPuJrTl3tlPESPeMI=","aNyqNMBRfRlmbmszrdZzUdjF9p6ZnKHjeTG8QQopdCg=","i6Uv56OVbFzTplajF0uTHTuyq7RVeL78WfKD7NgWpAU=","rZ49j2UGh3Cf1o9LkLQffYJaNlsCwjpjbO+IrCrADEM=","rqJFf5Ud6QoTLRowexOsEnXI/CSQ0nQ53UVxU9wDr48=","sLrd2WfnkQuQVpYiShE0yoMsUcth2RpqIiZSqCcVxj8=","tUHVRFIr5UqAt+N9TvxkCLdTpxQ7oQ8XDlYCpPq4wbo=","tnweAOC5Pppk0GviuW23tvSufzPvZ1bV8sL+taeCqEM=","y3nUSfgZS/a0A+QP2jGaHi+dKyEI97bLmBZi5jk+1rk=","4PtksQhcxVOJcBWNBaAJwk4nb7lOGgv2pSi0j7xP9SY=","70MRLGVDuI2yKDouB3J4wxWuLIRxmosl8lzIhWX76pk=","8K9W0sWkjWCkpbXJA+37fbOnNqlO1YnQt5ffM/+dPh0=","/Si5iGxEafNAYqQZIy9NW4AH15C4HMxtZ1/slB59gLs="]},"rlimitOps":[{"operation":"OPERATION_UPDATE","state":{"averageBlockNetUsage":{"lastOrdinal":135283641,"valueEx":"88270755","consumed":"121"},"averageBlockCpuUsage":{"lastOrdinal":135283641,"valueEx":"31428594","consumed":"42"},"pendingNetUsage":"1432","pendingCpuUsage":"542","totalNetWeight":"11510000000","totalCpuWeight":"11510000000","totalRamBytes":"6770088669","virtualNetLimit":"1048576","virtualCpuLimit":"400000"}},{"operation":"OPERATION_UPDATE","state":{"averageBlockNetUsage":{"lastOrdinal":135283642,"valueEx":"99468499","consumed":"1520"},"averageBlockCpuUsage":{"lastOrdinal":135283642,"valueEx":"35683356","consumed":"574"},"totalNetWeight":"11510000000","totalCpuWeight":"11510000000","totalRamBytes":"6770088669","virtualNetLimit":"1048576","virtualCpuLimit":"400000"}}],"filteredTransactions":[{"id":"3c29a78a06ed661cca3a7ca422dd6f525a254ce82738b2f9a3bfc2fc89729671","status":"TRANSACTIONSTATUS_EXECUTED","cpuUsageMicroSeconds":532,"netUsageWords":175,"packedTransaction":{"signatures":["SIG_K1_K9GADvJqW72sFezBsQHBwyWALe7cLuY51AGqp2keCpydmWWi51jU18iXwvZmrf3Apq9tFWx7ywfPdNzGKGdaE4FCZhQfy9"],"packedTransaction":"h5QgZ5FDgu+2IgAAAAABkBfIawLqMFUAADAAewVTMgEAQqW3AnNz1AAAAACo7TIyeoIDAAAAAAAABARHb2xkBnVpbnQzMgcBAEKltwJzc9QBBgAAAAAGTHVtYmVyBnVpbnQzMgcBAEKltwJzc9QAA1RhZwZzdHJpbmcHAQBCpbcCc3PUAQoEbm9uZQZSYXRpbmcHZmxvYXQzMgcBAEKltwJzc9QBCG3n+z0AAA=="}}],"unfilteredTransactionCount":1,"filteredTransactionCount":1,"filteredTransactionTraces":[{"id":"3c29a78a06ed661cca3a7ca422dd6f525a254ce82738b2f9a3bfc2fc89729671","blockNum":"135283642","index":"1","blockTime":"2024-10-29T07:53:14Z","producerBlockId":"081043bab79398dcd4d937a7e43200a7f5e2b14297fb57bb74624b3517b969e6","receipt":{"status":"TRANSACTIONSTATUS_EXECUTED","cpuUsageMicroSeconds":532,"netUsageWords":175},"elapsed":"578","netUsage":"1400","actionTraces":[{"receiver":"eosio.nft.ft","receipt":{"receiver":"eosio.nft.ft","digest":"9f673c2a229abd5acbb9cf5fc0a6d62f1329be16d211e20a81c51d1e51a72363","globalSequence":"140212037","authSequence":[{"accountName":"ultra.prop1","sequence":"267"}],"recvSequence":"8363","codeSequence":"16","abiSequence":"11"},"action":{"account":"eosio.nft.ft","name":"addkeys.a","authorization":[{"actor":"ultra.prop1","permission":"active"}],"jsonData":"{\"factory_id\":898,\"key_defs\":[{\"default_value\":[\"uint32\",0],\"edit_rights\":7,\"editors\":[\"ultra.prop1\"],\"name\":\"Gold\",\"type\":\"uint32\"},{\"default_value\":null,\"edit_rights\":7,\"editors\":[\"ultra.prop1\"],\"name\":\"Lumber\",\"type\":\"uint32\"},{\"default_value\":[\"string\",\"none\"],\"edit_rights\":7,\"editors\":[\"ultra.prop1\"],\"name\":\"Tag\",\"type\":\"string\"},{\"default_value\":[\"float32\",\"0.12300000339746475\"],\"edit_rights\":7,\"editors\":[\"ultra.prop1\"],\"name\":\"Rating\",\"type\":\"float32\"}],\"memo\":\"\"}","rawData":"ggMAAAAAAAAEBEdvbGQGdWludDMyBwEAQqW3AnNz1AEGAAAAAAZMdW1iZXIGdWludDMyBwEAQqW3AnNz1AADVGFnBnN0cmluZwcBAEKltwJzc9QBCgRub25lBlJhdGluZwdmbG9hdDMyBwEAQqW3AnNz1AEIbef7PQA="},"elapsed":"304","transactionId":"3c29a78a06ed661cca3a7ca422dd6f525a254ce82738b2f9a3bfc2fc89729671","blockNum":"135283642","producerBlockId":"081043bab79398dcd4d937a7e43200a7f5e2b14297fb57bb74624b3517b969e6","blockTime":"2024-10-29T07:53:14Z","accountRamDeltas":[{"account":"eosio.nftram","delta":"104"}],"actionOrdinal":1,"filteringMatched":true},{"receiver":"eosio.token","receipt":{"receiver":"eosio.token","digest":"94a910248d58e984d593edb6080ea2a1ab9cb14a878c1538ba6172560d7f602f","globalSequence":"140212038","authSequence":[{"accountName":"ultra.prop1","sequence":"268"}],"recvSequence":"14962","codeSequence":"3","abiSequence":"2"},"action":{"account":"eosio.token","name":"transfer","authorization":[{"actor":"ultra.prop1","permission":"active"}],"jsonData":"{\"from\":\"ultra.prop1\",\"to\":\"eosio.pool\",\"quantity\":\"0.89431979 UOS\",\"memo\":\"new key def ram payment for factory: 898\"}","rawData":"AEKltwJzc9QAQKS0AuowVaufVAUAAAAACFVPUwAAAAAobmV3IGtleSBkZWYgcmFtIHBheW1lbnQgZm9yIGZhY3Rvcnk6IDg5OA=="},"elapsed":"111","transactionId":"3c29a78a06ed661cca3a7ca422dd6f525a254ce82738b2f9a3bfc2fc89729671","blockNum":"135283642","producerBlockId":"081043bab79398dcd4d937a7e43200a7f5e2b14297fb57bb74624b3517b969e6","blockTime":"2024-10-29T07:53:14Z","actionOrdinal":2,"creatorActionOrdinal":1,"closestUnnotifiedAncestorActionOrdinal":1,"executionIndex":1},{"receiver":"ultra.prop1","receipt":{"receiver":"ultra.prop1","digest":"94a910248d58e984d593edb6080ea2a1ab9cb14a878c1538ba6172560d7f602f","globalSequence":"140212039","authSequence":[{"accountName":"ultra.prop1","sequence":"269"}],"recvSequence":"422","codeSequence":"3","abiSequence":"2"},"action":{"account":"eosio.token","name":"transfer","authorization":[{"actor":"ultra.prop1","permission":"active"}],"jsonData":"{\"from\":\"ultra.prop1\",\"to\":\"eosio.pool\",\"quantity\":\"0.89431979 UOS\",\"memo\":\"new key def ram payment for factory: 898\"}","rawData":"AEKltwJzc9QAQKS0AuowVaufVAUAAAAACFVPUwAAAAAobmV3IGtleSBkZWYgcmFtIHBheW1lbnQgZm9yIGZhY3Rvcnk6IDg5OA=="},"elapsed":"2","transactionId":"3c29a78a06ed661cca3a7ca422dd6f525a254ce82738b2f9a3bfc2fc89729671","blockNum":"135283642","producerBlockId":"081043bab79398dcd4d937a7e43200a7f5e2b14297fb57bb74624b3517b969e6","blockTime":"2024-10-29T07:53:14Z","actionOrdinal":3,"creatorActionOrdinal":2,"closestUnnotifiedAncestorActionOrdinal":2,"executionIndex":2},{"receiver":"eosio.pool","receipt":{"receiver":"eosio.pool","digest":"94a910248d58e984d593edb6080ea2a1ab9cb14a878c1538ba6172560d7f602f","globalSequence":"140212040","authSequence":[{"accountName":"ultra.prop1","sequence":"270"}],"recvSequence":"186","codeSequence":"3","abiSequence":"2"},"action":{"account":"eosio.token","name":"transfer","authorization":[{"actor":"ultra.prop1","permission":"active"}],"jsonData":"{\"from\":\"ultra.prop1\",\"to\":\"eosio.pool\",\"quantity\":\"0.89431979 UOS\",\"memo\":\"new key def ram payment for factory: 898\"}","rawData":"AEKltwJzc9QAQKS0AuowVaufVAUAAAAACFVPUwAAAAAobmV3IGtleSBkZWYgcmFtIHBheW1lbnQgZm9yIGZhY3Rvcnk6IDg5OA=="},"elapsed":"2","transactionId":"3c29a78a06ed661cca3a7ca422dd6f525a254ce82738b2f9a3bfc2fc89729671","blockNum":"135283642","producerBlockId":"081043bab79398dcd4d937a7e43200a7f5e2b14297fb57bb74624b3517b969e6","blockTime":"2024-10-29T07:53:14Z","actionOrdinal":4,"creatorActionOrdinal":2,"closestUnnotifiedAncestorActionOrdinal":2,"executionIndex":3}],"dbOps":[{"operation":"OPERATION_UPDATE","code":"eosio.nft.ft","scope":"eosio.nft.ft","tableName":"factory.b","primaryKey":"..........1s2","oldPayer":"eosio.nftram","newPayer":"eosio.nftram","oldData":"ggMAAAAAAAAAQqW3AnNz1ABCpbcCc3PUAAAAAAAAAAAIVU9TAAAAAAAAAAAAAAAAAAAEdGVzdNV2j44qexqKl3TftTjgoZKNDZrF8IvXgcIUWbQwjcUjAAAAAAAAAAAAAAAAAAV0ZXN0MgHVdo+OKnsaipd037U44KGSjQ2axfCL14HCFFm0MI3FIwAA","newData":"ggMAAAAAAAAAQqW3AnNz1ABCpbcCc3PUAAAAAAAAAAAIVU9TAAAAAAAAAAAAAAAAAAAEdGVzdNV2j44qexqKl3TftTjgoZKNDZrF8IvXgcIUWbQwjcUjAAAAAAAAAAAAAAAAAAV0ZXN0MgHVdo+OKnsaipd037U44KGSjQ2axfCL14HCFFm0MI3FIwABBARHb2xkBgcBAEKltwJzc9QBBgAAAAAGTHVtYmVyBgcBAEKltwJzc9QAA1RhZwoHAQBCpbcCc3PUAQoEbm9uZQZSYXRpbmcIBwEAQqW3AnNz1AEIbef7PeEBAAAAAAAAmQAAAAAAAAA="},{"operation":"OPERATION_UPDATE","actionIndex":1,"code":"eosio.token","scope":"ultra.prop1","tableName":"accounts","primaryKey":"........ehbp5","oldPayer":"eosio","newPayer":"eosio","oldData":"Vzh0ZxYAAAAIVU9TAAAAAA==","newData":"rJgfYhYAAAAIVU9TAAAAAA=="},{"operation":"OPERATION_UPDATE","actionIndex":1,"code":"eosio.token","scope":"eosio.pool","tableName":"accounts","primaryKey":"........ehbp5","oldPayer":"eosio","newPayer":"eosio","oldData":"/JBsWRMAAAAIVU9TAAAAAA==","newData":"pzDBXhMAAAAIVU9TAAAAAA=="}],"ramOps":[{"operation":"OPERATION_PRIMARY_INDEX_UPDATE","payer":"eosio.nftram","delta":"104","usage":"1828966","namespace":"NAMESPACE_TABLE_ROW","uniqueKey":"eosio.nft.ft:eosio.nft.ft:factory.b:..........1s2","action":"ACTION_UPDATE"}],"rlimitOps":[{"operation":"OPERATION_UPDATE","accountUsage":{"owner":"ultra.prop1","netUsage":{"lastOrdinal":1567007188,"valueEx":"2723191","consumed":"1403"},"cpuUsage":{"lastOrdinal":1567007188,"valueEx":"1038780","consumed":"534"},"ramUsage":"1406"}}],"creationTree":[{"creatorActionIndex":-1},{"executionActionIndex":1},{"creatorActionIndex":1,"executionActionIndex":2},{"creatorActionIndex":1,"executionActionIndex":3}]}],"unfilteredTransactionTraceCount":2,"filteredTransactionTraceCount":1,"unfilteredExecutedInputActionCount":2,"filteredExecutedInputActionCount":1,"unfilteredExecutedTotalActionCount":5,"filteredExecutedTotalActionCount":1,"validBlockSigningAuthorityV2":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS8dajNwmCPVfr2cNzyWZDwucn5CCPV7nRCYoBB2XAcgCEBWRctX","weight":1}]}},"activeScheduleV2":{"version":19,"producers":[{"accountName":"produceracc1","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS8dajNwmCPVfr2cNzyWZDwucn5CCPV7nRCYoBB2XAcgCEBWRctX","weight":1}]}}},{"accountName":"produceracc2","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS8dajNwmCPVfr2cNzyWZDwucn5CCPV7nRCYoBB2XAcgCEBWRctX","weight":1}]}}},{"accountName":"produceracc3","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS8dajNwmCPVfr2cNzyWZDwucn5CCPV7nRCYoBB2XAcgCEBWRctX","weight":1}]}}}]},"filteringApplied":true,"filteringIncludeFilterExpr":"(account==\"eosio.nft.ft\" \u0026\u0026 receiver==\"eosio.nft.ft\") || (action==\"setabi\" \u0026\u0026 account==\"eosio\" \u0026\u0026 data.account==\"eosio.nft.ft\")"} \ No newline at end of file diff --git a/testdata/block-220236206.pb.json b/testdata/block-220236206.pb.json new file mode 100644 index 0000000..b0d6f20 --- /dev/null +++ b/testdata/block-220236206.pb.json @@ -0,0 +1,18491 @@ +{ + "id": "0d2089aefa193ad8603a47c7a968ba9393f6c0cb33ece3f7be0293c4d453bd27", + "number": 220236206, + "version": 1, + "header": { + "timestamp": "2024-11-22T12:25:36.500Z", + "producer": "eosriobrazil", + "previous": "0d2089ad415f8831b59bc455660422b97a7a212e2faddd0deb28f007bcc7d2b9", + "transactionMroot": "JRZUcljx8X4hBvRAMsWDwwXCGAtptG7r7a5pJPAWYN4=", + "actionMroot": "Ja3GV0vO1agjOJ/ihCxIOALdAVa5bVnu0bbdMOjda6g=", + "scheduleVersion": 31 + }, + "producerSignature": "SIG_K1_K72eNTw77VdwXqsjbrMh6MAc7Q8cCDGFvWcVrkytzy8ZfAw3Y3YrgnviMMWPefZ35o1VyB6WYExUQ5gWthyokccNMEiiU7", + "dposProposedIrreversibleBlocknum": 220236168, + "dposIrreversibleBlocknum": 220236120, + "blockrootMerkle": { + "nodeCount": 220236205, + "activeNodes": [ + "DSCJrUFfiDG1m8RVZgQiuXp6IS4vrd0N6yjwB7zH0rk=", + "aXe1uKkMg+VbhnaYMygEsfqPbOg6tWEyCXtMLeEIRGk=", + "72Bafg/+Z6XE1DDnkFPbE7O/KJPhYxfx1K47r0/0NSM=", + "PXD0gMYVD96mIs2E6ltCSG5x80ElhheY88yQUrxSEjE=", + "JROhAt9Wz4nW4xithGmvZqls0Sa6qAxvSYXOlT6kKbI=", + "Zboa8xJod/ptDYiW82wVOOjJDQWrbXQ1MLzHEQphoz8=", + "R8RQcU54g6toR/5hAlugOTRjMG7jHYZnvsrY1Nja5Qo=", + "aiXhMLZLct75F92b4Mp/mRSWYZpQuGrqahhchL4j7EQ=", + "d1cNTAOQnbatL4DoIgOhVh7Ts0rJz7LLRvKk4lnK4JQ=", + "/ENxDwK0XL8tXCx+zaVGe+RQCOHc2HJm/XVkV/saYfA=", + "+ua9tboDQTQxoiZBSXGQokM8Wvwz0wR5n70Li9Jt6Ko=", + "7W4tRMnX8pc8P35tHJ66qZKQD96xG4h9pQzZ3l7BXnU=", + "Hicc4UCVlxcMdgJCSRrEWKOQVgX8EWqnWAa0asJ0GEc=" + ] + }, + "producerToLastProduced": [ + { + "name": "cryptolions1", + "lastBlockNumProduced": 220236144 + }, + { + "name": "eosnationftw", + "lastBlockNumProduced": 220236180 + }, + { + "name": "eosriobrazil", + "lastBlockNumProduced": 220236206 + }, + { + "name": "eosubisoft1", + "lastBlockNumProduced": 220236156 + }, + { + "name": "ivote4eosusa", + "lastBlockNumProduced": 220236192 + }, + { + "name": "uoseouldotio", + "lastBlockNumProduced": 220236204 + }, + { + "name": "uosswedenorg", + "lastBlockNumProduced": 220236168 + } + ], + "producerToLastImpliedIrb": [ + { + "name": "cryptolions1", + "lastBlockNumProduced": 220236096 + }, + { + "name": "eosnationftw", + "lastBlockNumProduced": 220236132 + }, + { + "name": "eosriobrazil", + "lastBlockNumProduced": 220236168 + }, + { + "name": "eosubisoft1", + "lastBlockNumProduced": 220236108 + }, + { + "name": "ivote4eosusa", + "lastBlockNumProduced": 220236144 + }, + { + "name": "uoseouldotio", + "lastBlockNumProduced": 220236156 + }, + { + "name": "uosswedenorg", + "lastBlockNumProduced": 220236120 + } + ], + "confirmCount": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 4, + 4 + ], + "pendingSchedule": { + "scheduleLibNum": 198248813, + "scheduleHash": "KAfHt4yIHJ3f/BADa8/YxGoStHfgBQ8jYxZwEOm/bqk=", + "scheduleV2": { + "version": 31 + } + }, + "activatedProtocolFeatures": { + "protocolFeatures": [ + "DsfggBd7LAKyeNUIhhFoa0nXOZJaktm/ys1/xrdAU70=", + "D+GFuIMhUYZYxU8h6R14fymGnFvA3T/HgA95iDwUAw0=", + "GltEdlcoFG1xuRadzlKjHV+sIUO2GwCLfKkyj92LrEU=", + "GpmlnYfgbgnsWwKKnLt3SbSlrYgZAENl0C3EN5qLckE=", + "JlL1+WAGKUEJs90LveY2k/VTJK9FK3me4TeoGpBe7SU=", + "KZ3LavaSMkuJmznxbVpTCjMGKATkHwncl+nxVrRHZwc=", + "SpDADVVFTcWwWQVcohNXnG6oVpZ3EqVgF0h4hqTUzA8=", + "TnvzSNoAqUVImypoF0nrVvXeALkAAU4Tfdrjn0j2nWc=", + "T8qL2Cu9GB5xTig/g+G0XZXKWvQPuJrTl3tlPESPeMI=", + "aNyqNMBRfRlmbmszrdZzUdjF9p6ZnKHjeTG8QQopdCg=", + "hhD9I0EwDu2GzgKTqCX+51OHZNeQlH85Frjk0ANrxMQ=", + "i6Uv56OVbFzTplajF0uTHTuyq7RVeL78WfKD7NgWpAU=", + "rZ49j2UGh3Cf1o9LkLQffYJaNlsCwjpjbO+IrCrADEM=", + "rqJFf5Ud6QoTLRowexOsEnXI/CSQ0nQ53UVxU9wDr48=", + "sLrd2WfnkQuQVpYiShE0yoMsUcth2RpqIiZSqCcVxj8=", + "tUHVRFIr5UqAt+N9TvxkCLdTpxQ7oQ8XDlYCpPq4wbo=", + "tnweAOC5Pppk0GviuW23tvSufzPvZ1bV8sL+taeCqEM=", + "y3nUSfgZS/a0A+QP2jGaHi+dKyEI97bLmBZi5jk+1rk=", + "4PtksQhcxVOJcBWNBaAJwk4nb7lOGgv2pSi0j7xP9SY=", + "70MRLGVDuI2yKDouB3J4wxWuLIRxmosl8lzIhWX76pk=", + "8K9W0sWkjWCkpbXJA+37fbOnNqlO1YnQt5ffM/+dPh0=", + "/Si5iGxEafNAYqQZIy9NW4AH15C4HMxtZ1/slB59gLs=" + ] + }, + "rlimitOps": [ + { + "operation": "OPERATION_UPDATE", + "state": { + "averageBlockNetUsage": { + "lastOrdinal": 220236205, + "valueEx": "20457003", + "consumed": "29" + }, + "averageBlockCpuUsage": { + "lastOrdinal": 220236205, + "valueEx": "5706720", + "consumed": "7" + }, + "pendingNetUsage": "336", + "pendingCpuUsage": "125", + "totalNetWeight": "3251000000", + "totalCpuWeight": "3251000000", + "totalRamBytes": "8002833871", + "virtualNetLimit": "1048576", + "virtualCpuLimit": "400000" + } + }, + { + "operation": "OPERATION_UPDATE", + "state": { + "averageBlockNetUsage": { + "lastOrdinal": 220236206, + "valueEx": "23086527", + "consumed": "357" + }, + "averageBlockCpuUsage": { + "lastOrdinal": 220236206, + "valueEx": "6700831", + "consumed": "131" + }, + "totalNetWeight": "3251000000", + "totalCpuWeight": "3251000000", + "totalRamBytes": "8002833871", + "virtualNetLimit": "1048576", + "virtualCpuLimit": "400000" + } + } + ], + "filteredTransactions": [ + { + "id": "74e697740232d64b8089733494d9ed946a23d835f192cf5eb8f81ad2621220cf", + "status": "TRANSACTIONSTATUS_EXECUTED", + "cpuUsageMicroSeconds": 124, + "netUsageWords": 41, + "packedTransaction": { + "signatures": [ + "SIG_K1_KgTLgAcQJebVNkpKscpr6DwBWTCmHQBne6GpHYLqY2g4yuQij2o8LMBezftkFpZLB22KUiDwigEG5dwivCHDmZ1dpUoco1" + ], + "packedTransaction": "XXhAZ0yJN5LcDgAAAAABAI657AJzc9Rwsc7YRZVd4wGQHjFmGCOMCQAAAACo7TIySBAyVjmp62pXAxREjj3kHItSdQxmT1f+AjpzC8wAR30AAAAAAAAACAAdMWYYI4wJXgEAAAAAAAAIQGYbEwuEwq84BAAAAAAAAAA=" + } + } + ], + "unfilteredTransactionCount": 1, + "filteredTransactionCount": 1, + "filteredTransactionTraces": [ + { + "id": "74e697740232d64b8089733494d9ed946a23d835f192cf5eb8f81ad2621220cf", + "blockNum": "220236206", + "index": "1", + "blockTime": "2024-11-22T12:25:36.500Z", + "producerBlockId": "0d2089aefa193ad8603a47c7a968ba9393f6c0cb33ece3f7be0293c4d453bd27", + "receipt": { + "status": "TRANSACTIONSTATUS_EXECUTED", + "cpuUsageMicroSeconds": 124, + "netUsageWords": 41 + }, + "elapsed": "279", + "netUsage": "328", + "actionTraces": [ + { + "receiver": "ultra.rgrab", + "receipt": { + "receiver": "ultra.rgrab", + "digest": "f998c42392cf421b9e983f835ec418f4873358528009532a1b3d7d4ce9a0134d", + "globalSequence": "413618825", + "authSequence": [ + { + "accountName": "1aa2aa3aa4jd", + "sequence": "776446" + } + ], + "recvSequence": "11", + "codeSequence": "4", + "abiSequence": "2" + }, + "action": { + "account": "ultra.rgrab", + "name": "whitelistusr", + "authorization": [ + { + "actor": "1aa2aa3aa4jd", + "permission": "active" + } + ], + "jsonData": "{\"campaign\":\"expiredtest1\",\"wallets\":[{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":125,\"wallet_id\":\"448e3de41c8b52750c664f57fe023a730bcc0047\"},{\"points\":350,\"wallet_id\":\"001d316618238c09\"},{\"points\":1080,\"wallet_id\":\"40661b130b84c2af\"}]}", + "rawData": "EDJWOanralcDFESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAAIAB0xZhgjjAleAQAAAAAAAAhAZhsTC4TCrzgEAAAAAAAA" + }, + "elapsed": "214", + "transactionId": "74e697740232d64b8089733494d9ed946a23d835f192cf5eb8f81ad2621220cf", + "blockNum": "220236206", + "producerBlockId": "0d2089aefa193ad8603a47c7a968ba9393f6c0cb33ece3f7be0293c4d453bd27", + "blockTime": "2024-11-22T12:25:36.500Z", + "accountRamDeltas": [ + { + "account": "1aa2aa3aa4jd", + "delta": "511" + } + ], + "actionOrdinal": 1, + "filteringMatched": true + } + ], + "dbOps": [ + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "ipdcqbb4bqb44", + "newPayer": "1aa2aa3aa4jd", + "newData": "FESOPeQci1J1DGZPV/4COnMLzABHfQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "1aa2aa3aa4ik", + "newPayer": "1aa2aa3aa4jd", + "newData": "CAAdMWYYI4wJXgEAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist", + "primaryKey": "pz1cc2sn3hn4", + "newPayer": "1aa2aa3aa4jd", + "newData": "CEBmGxMLhMKvOAQAAAAAAAA=" + } + ], + "ramOps": [ + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "1aa2aa3aa4jd", + "delta": "112", + "usage": "1776772", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_PRIMARY_INDEX_ADD", + "payer": "1aa2aa3aa4jd", + "delta": "141", + "usage": "1776913", + "namespace": "NAMESPACE_TABLE_ROW", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist:ipdcqbb4bqb44", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_PRIMARY_INDEX_ADD", + "payer": "1aa2aa3aa4jd", + "delta": "129", + "usage": "1777042", + "namespace": "NAMESPACE_TABLE_ROW", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist:1aa2aa3aa4ik", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_PRIMARY_INDEX_ADD", + "payer": "1aa2aa3aa4jd", + "delta": "129", + "usage": "1777171", + "namespace": "NAMESPACE_TABLE_ROW", + "uniqueKey": "ultra.rgrab:expiredtest1:whitelist:pz1cc2sn3hn4", + "action": "ACTION_ADD" + } + ], + "rlimitOps": [ + { + "operation": "OPERATION_UPDATE", + "accountUsage": { + "owner": "1aa2aa3aa4jd", + "netUsage": { + "lastOrdinal": 1571187073, + "valueEx": "19566", + "consumed": "329" + }, + "cpuUsage": { + "lastOrdinal": 1571187073, + "valueEx": "7425", + "consumed": "125" + }, + "ramUsage": "1777171" + } + } + ], + "tableOps": [ + { + "operation": "OPERATION_INSERT", + "payer": "1aa2aa3aa4jd", + "code": "ultra.rgrab", + "scope": "expiredtest1", + "tableName": "whitelist" + } + ], + "creationTree": [ + { + "creatorActionIndex": -1 + } + ] + } + ], + "unfilteredTransactionTraceCount": 2, + "filteredTransactionTraceCount": 1, + "unfilteredExecutedInputActionCount": 2, + "filteredExecutedInputActionCount": 1, + "unfilteredExecutedTotalActionCount": 2, + "filteredExecutedTotalActionCount": 1, + "validBlockSigningAuthorityV2": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS7nq54FcLXtDtY8GHqknYk5NqZPZNBVY3wCJQeRrzVzVjyXsEM6", + "weight": 1 + } + ] + } + }, + "activeScheduleV2": { + "version": 31, + "producers": [ + { + "accountName": "eosriobrazil", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS7nq54FcLXtDtY8GHqknYk5NqZPZNBVY3wCJQeRrzVzVjyXsEM6", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "cryptolions1", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS8LioNSB3ugUG7LweRBB2BR8AMJUEFGicXMs9msHByd9vMmxEKe", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "eosubisoft1", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5FgTg6xtmMaonVo3uo4S8oLTWTEUnSagAMQCvUkczu9e5hXaWp", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "uosswedenorg", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS6j7tqvStvcykc1ysDuNY1J8PqyARpGqWrxbVjiuNBQL8uL7r4A", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "eosnationftw", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5uMMtMvBp4pPEBiUsfdBR9LVnJnzUonGFvQtwnh1eoXzZZKN8U", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "ivote4eosusa", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5gKJLRWxMgLWDsvKu7YaTweoQpqin5SSzJ4mDuaXnQ6tZb1F31", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "uoseouldotio", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS7Tauu774Uz1bisTGUC37NsS6mxYoNaW3QKYF3oD1bUUJdCGxKf", + "weight": 1 + } + ] + } + } + } + ] + }, + "filteringApplied": true, + "filteringIncludeFilterExpr": "(account==\"ultra.rgrab\" \u0026\u0026 receiver==\"ultra.rgrab\") || (action==\"setabi\" \u0026\u0026 account==\"eosio\" \u0026\u0026 data.account==\"ultra.rgrab\")" +} \ No newline at end of file diff --git a/testdata/block-224785515.pb.json b/testdata/block-224785515.pb.json new file mode 100644 index 0000000..55be394 --- /dev/null +++ b/testdata/block-224785515.pb.json @@ -0,0 +1,554 @@ +{ + "id": "0d65f46b04f618e62675c2d693c6a3594dca5b5f946d802f7bc44c85e86ba3ea", + "number": 224785515, + "version": 1, + "header": { + "timestamp": "2025-01-10T15:46:01Z", + "producer": "eosioubisoft", + "previous": "0d65f46a6c53954c4e1d4ce4643eded6d524ca2b0b65904541a4edcf86caa7bb", + "transactionMroot": "3mz9nlYC7GD06Ukx5nfJaiSVfTH9TpW+W5HvjnMNu5o=", + "actionMroot": "Kc2tA6FOHRafTCzU4fGxR9Qh3gDyMjt28QrfTknqdRc=", + "scheduleVersion": 19 + }, + "producerSignature": "SIG_K1_K9GibDvLX25vouHemh1yRPuU3N7RTSHWzQ5N4gGPo9vjHmfB5QLSu4GURDbJPaX33bVprYSj7J8KEQ8MWptzYbsL6Eb1wJ", + "dposProposedIrreversibleBlocknum": 224785476, + "dposIrreversibleBlocknum": 224785428, + "blockrootMerkle": { + "nodeCount": 224785514, + "activeNodes": [ + "ynbJN4fpsJ0seMlwAe3NMhKo91yO/6W+3VNjTkKFFJY=", + "01fYjzvKVTT2W5pqlQC2xLmVT/mwzLpF+B4IwQ8NHQg=", + "0fOQJT/s4ADHGPRC+j6JxDoYIe6gYwfSKsZzgr/XIvQ=", + "0O/fFsW1+o8VJ0MQrOpE/jhuQfLc/sVQp8Ot4IY1Rn0=", + "6P5+EZof9ovqhTZTzlRw3cNimgjrre30cOOxmI6fYQY=", + "kuuUSzw4+akyjW/YacPuPatgKW8hhFLZf0PelcGSmsM=", + "tmn/3IiF+4GTJrw95q/4CGilNzNpzoWORyJOEQLtwOo=", + "vOaPbooTZj7CecjLOheP6C/r9Fkt5k7NulJnMz/YAYs=", + "dVYFxNk8xKCZT0k8MhwBw+msWS0gOjoQA6hCIbxbdlE=", + "Pp7uXqBchRnfVCobYwVDQ8BFsTL+Ty83uVWYVe1j4qk=", + "20gooaM/sptXvZyE0qpVbzKO3K+FVnRiWw4fxozqDg0=", + "5hrRC88/TttFtrpgM5QZZWjIgPV5ud+v+Gjz8s1rTWA=", + "YzEwPxGINdf9rgIVVsUQXSkqbZX7rofPEv9UOxtp4Ws=", + "UyioFOAVc+G2ljacf97l9R2OMKFs1PyRAbbEJEP1xGY=", + "v2lXY2h8Gp4EFhhwCS/aHJ6HMHwrp4Tzv3DTBQR0b3I=", + "6wxQEY3S/RwkSVOtbgd70qou3/KL5/sKo/LzAAjVnno=", + "w80kyMKG7R//Y3c7vRdR9CXmALCjU3BNSY7VAiYmMRo=" + ] + }, + "producerToLastProduced": [ + { + "name": "cryptolions1", + "lastBlockNumProduced": 224785512 + }, + { + "name": "eosioubisoft", + "lastBlockNumProduced": 224785515 + }, + { + "name": "eosnationftw", + "lastBlockNumProduced": 224785464 + }, + { + "name": "eosriobrazil", + "lastBlockNumProduced": 224785500 + }, + { + "name": "ivote4eosusa", + "lastBlockNumProduced": 224785476 + }, + { + "name": "uoseouldotio", + "lastBlockNumProduced": 224785488 + }, + { + "name": "uosswedenorg", + "lastBlockNumProduced": 224785452 + } + ], + "producerToLastImpliedIrb": [ + { + "name": "cryptolions1", + "lastBlockNumProduced": 224785464 + }, + { + "name": "eosioubisoft", + "lastBlockNumProduced": 224785476 + }, + { + "name": "eosnationftw", + "lastBlockNumProduced": 224785416 + }, + { + "name": "eosriobrazil", + "lastBlockNumProduced": 224785452 + }, + { + "name": "ivote4eosusa", + "lastBlockNumProduced": 224785428 + }, + { + "name": "uoseouldotio", + "lastBlockNumProduced": 224785440 + }, + { + "name": "uosswedenorg", + "lastBlockNumProduced": 224785404 + } + ], + "confirmCount": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 4, + 4, + 4 + ], + "pendingSchedule": { + "scheduleLibNum": 146119709, + "scheduleHash": "AGsM1+BObSU5Qsy6ZKVw0LseKGWLAS24+YspBvloWbA=", + "scheduleV2": { + "version": 19 + } + }, + "activatedProtocolFeatures": { + "protocolFeatures": [ + "DsfggBd7LAKyeNUIhhFoa0nXOZJaktm/ys1/xrdAU70=", + "D+GFuIMhUYZYxU8h6R14fymGnFvA3T/HgA95iDwUAw0=", + "GltEdlcoFG1xuRadzlKjHV+sIUO2GwCLfKkyj92LrEU=", + "GpmlnYfgbgnsWwKKnLt3SbSlrYgZAENl0C3EN5qLckE=", + "JlL1+WAGKUEJs90LveY2k/VTJK9FK3me4TeoGpBe7SU=", + "KZ3LavaSMkuJmznxbVpTCjMGKATkHwncl+nxVrRHZwc=", + "SpDADVVFTcWwWQVcohNXnG6oVpZ3EqVgF0h4hqTUzA8=", + "TnvzSNoAqUVImypoF0nrVvXeALkAAU4Tfdrjn0j2nWc=", + "T8qL2Cu9GB5xTig/g+G0XZXKWvQPuJrTl3tlPESPeMI=", + "aNyqNMBRfRlmbmszrdZzUdjF9p6ZnKHjeTG8QQopdCg=", + "hhD9I0EwDu2GzgKTqCX+51OHZNeQlH85Frjk0ANrxMQ=", + "i6Uv56OVbFzTplajF0uTHTuyq7RVeL78WfKD7NgWpAU=", + "rZ49j2UGh3Cf1o9LkLQffYJaNlsCwjpjbO+IrCrADEM=", + "rqJFf5Ud6QoTLRowexOsEnXI/CSQ0nQ53UVxU9wDr48=", + "sLrd2WfnkQuQVpYiShE0yoMsUcth2RpqIiZSqCcVxj8=", + "tUHVRFIr5UqAt+N9TvxkCLdTpxQ7oQ8XDlYCpPq4wbo=", + "tnweAOC5Pppk0GviuW23tvSufzPvZ1bV8sL+taeCqEM=", + "y3nUSfgZS/a0A+QP2jGaHi+dKyEI97bLmBZi5jk+1rk=", + "4PtksQhcxVOJcBWNBaAJwk4nb7lOGgv2pSi0j7xP9SY=", + "70MRLGVDuI2yKDouB3J4wxWuLIRxmosl8lzIhWX76pk=", + "8K9W0sWkjWCkpbXJA+37fbOnNqlO1YnQt5ffM/+dPh0=", + "/Si5iGxEafNAYqQZIy9NW4AH15C4HMxtZ1/slB59gLs=" + ] + }, + "rlimitOps": [ + { + "operation": "OPERATION_UPDATE", + "state": { + "averageBlockNetUsage": { + "lastOrdinal": 224785514, + "valueEx": "24481880", + "consumed": "33" + }, + "averageBlockCpuUsage": { + "lastOrdinal": 224785514, + "valueEx": "7263980", + "consumed": "9" + }, + "pendingNetUsage": "696", + "pendingCpuUsage": "262", + "totalNetWeight": "11663000000", + "totalCpuWeight": "11663000000", + "totalRamBytes": "6888821482", + "virtualNetLimit": "1048576", + "virtualCpuLimit": "400000" + } + }, + { + "operation": "OPERATION_UPDATE", + "state": { + "averageBlockNetUsage": { + "lastOrdinal": 224785515, + "valueEx": "30077864", + "consumed": "721" + }, + "averageBlockCpuUsage": { + "lastOrdinal": 224785515, + "valueEx": "9386780", + "consumed": "270" + }, + "totalNetWeight": "11663000000", + "totalCpuWeight": "11663000000", + "totalRamBytes": "6888821482", + "virtualNetLimit": "1048576", + "virtualCpuLimit": "400000" + } + } + ], + "filteredTransactions": [ + { + "id": "fb1f56096df0b076ad8429c6c3146092b9b03f290219ebe62f7b227d5f6689d3", + "status": "TRANSACTIONSTATUS_EXECUTED", + "cpuUsageMicroSeconds": 261, + "netUsageWords": 86, + "packedTransaction": { + "signatures": [ + "SIG_K1_KYFWhV8SQqv9u6jG6nnFbhkUUPrBicdjCYC81LiJFwfFX6gcYdbcwh6a78eG6pwGncU5dYdUsvTEukGLQz2DAzWTA8WMDx" + ], + "compression": 1, + "packedContextFreeData": "eJxjAAAAAQAB", + "packedTransaction": "eJwTcmxM5/gix346hAEIGBmWNZkwvzIIBbLDdW3OnmVkcJjKxlhcfAUku+KtkZEVXKBv5xsmIH2Are2pGVCSI9Q/GKRIMj05tbK4JL8qMUXHyNTYVMcISBoYW5hbMAAAoDgerQ==" + } + } + ], + "unfilteredTransactionCount": 1, + "filteredTransactionCount": 1, + "filteredTransactionTraces": [ + { + "id": "fb1f56096df0b076ad8429c6c3146092b9b03f290219ebe62f7b227d5f6689d3", + "blockNum": "224785515", + "index": "1", + "blockTime": "2025-01-10T15:46:01Z", + "producerBlockId": "0d65f46b04f618e62675c2d693c6a3594dca5b5f946d802f7bc44c85e86ba3ea", + "receipt": { + "status": "TRANSACTIONSTATUS_EXECUTED", + "cpuUsageMicroSeconds": 261, + "netUsageWords": 86 + }, + "elapsed": "1828", + "netUsage": "688", + "actionTraces": [ + { + "receiver": "eosio.token", + "receipt": { + "receiver": "eosio.token", + "digest": "35c73e7591a00b074998de84f054c64378f6daf989bedf45d9b2c99c204e9bc3", + "globalSequence": "408070916", + "authSequence": [ + { + "accountName": "ultra.camp", + "sequence": "3" + } + ], + "recvSequence": "195062", + "codeSequence": "5", + "abiSequence": "5" + }, + "action": { + "account": "eosio.token", + "name": "transfer", + "authorization": [ + { + "actor": "ultra.camp", + "permission": "active" + } + ], + "jsonData": "{\"from\":\"ultra.camp\",\"to\":\"ultra.rgrab\",\"quantity\":\"2357.79000000 UOS\",\"memo\":\"gceystozad,2535,225303878\"}", + "rawData": "AECVBgFzc9QAjrnsAnNz1MAGhuU2AAAACFVPUwAAAAAZZ2NleXN0b3phZCwyNTM1LDIyNTMwMzg3OA==" + }, + "elapsed": "1432", + "transactionId": "fb1f56096df0b076ad8429c6c3146092b9b03f290219ebe62f7b227d5f6689d3", + "blockNum": "224785515", + "producerBlockId": "0d65f46b04f618e62675c2d693c6a3594dca5b5f946d802f7bc44c85e86ba3ea", + "blockTime": "2025-01-10T15:46:01Z", + "actionOrdinal": 1, + "filteringMatched": true + }, + { + "receiver": "ultra.camp", + "receipt": { + "receiver": "ultra.camp", + "digest": "35c73e7591a00b074998de84f054c64378f6daf989bedf45d9b2c99c204e9bc3", + "globalSequence": "408070917", + "authSequence": [ + { + "accountName": "ultra.camp", + "sequence": "4" + } + ], + "recvSequence": "2", + "codeSequence": "5", + "abiSequence": "5" + }, + "action": { + "account": "eosio.token", + "name": "transfer", + "authorization": [ + { + "actor": "ultra.camp", + "permission": "active" + } + ], + "jsonData": "{\"from\":\"ultra.camp\",\"to\":\"ultra.rgrab\",\"quantity\":\"2357.79000000 UOS\",\"memo\":\"gceystozad,2535,225303878\"}", + "rawData": "AECVBgFzc9QAjrnsAnNz1MAGhuU2AAAACFVPUwAAAAAZZ2NleXN0b3phZCwyNTM1LDIyNTMwMzg3OA==" + }, + "elapsed": "6", + "transactionId": "fb1f56096df0b076ad8429c6c3146092b9b03f290219ebe62f7b227d5f6689d3", + "blockNum": "224785515", + "producerBlockId": "0d65f46b04f618e62675c2d693c6a3594dca5b5f946d802f7bc44c85e86ba3ea", + "blockTime": "2025-01-10T15:46:01Z", + "actionOrdinal": 2, + "creatorActionOrdinal": 1, + "closestUnnotifiedAncestorActionOrdinal": 1, + "executionIndex": 1, + "filteringMatched": true + }, + { + "receiver": "ultra.rgrab", + "receipt": { + "receiver": "ultra.rgrab", + "digest": "35c73e7591a00b074998de84f054c64378f6daf989bedf45d9b2c99c204e9bc3", + "globalSequence": "408070918", + "authSequence": [ + { + "accountName": "ultra.camp", + "sequence": "5" + } + ], + "recvSequence": "8", + "codeSequence": "5", + "abiSequence": "5" + }, + "action": { + "account": "eosio.token", + "name": "transfer", + "authorization": [ + { + "actor": "ultra.camp", + "permission": "active" + } + ], + "jsonData": "{\"from\":\"ultra.camp\",\"to\":\"ultra.rgrab\",\"quantity\":\"2357.79000000 UOS\",\"memo\":\"gceystozad,2535,225303878\"}", + "rawData": "AECVBgFzc9QAjrnsAnNz1MAGhuU2AAAACFVPUwAAAAAZZ2NleXN0b3phZCwyNTM1LDIyNTMwMzg3OA==" + }, + "elapsed": "224", + "transactionId": "fb1f56096df0b076ad8429c6c3146092b9b03f290219ebe62f7b227d5f6689d3", + "blockNum": "224785515", + "producerBlockId": "0d65f46b04f618e62675c2d693c6a3594dca5b5f946d802f7bc44c85e86ba3ea", + "blockTime": "2025-01-10T15:46:01Z", + "actionOrdinal": 3, + "creatorActionOrdinal": 1, + "closestUnnotifiedAncestorActionOrdinal": 1, + "executionIndex": 2, + "filteringMatched": true + } + ], + "dbOps": [ + { + "operation": "OPERATION_UPDATE", + "code": "eosio.token", + "scope": "ultra.camp", + "tableName": "accounts", + "primaryKey": "........ehbp5", + "oldPayer": "eosio.token", + "newPayer": "eosio.token", + "oldData": "AMAlyy4CAAAIVU9TAAAAAA==", + "newData": "QLmf5fcBAAAIVU9TAAAAAA==" + }, + { + "operation": "OPERATION_UPDATE", + "code": "eosio.token", + "scope": "ultra.rgrab", + "tableName": "accounts", + "primaryKey": "........ehbp5", + "oldPayer": "eosio.token", + "newPayer": "eosio.token", + "oldData": "AAAAAAAAAAAIVU9TAAAAAA==", + "newData": "wAaG5TYAAAAIVU9TAAAAAA==" + }, + { + "operation": "OPERATION_UPDATE", + "actionIndex": 2, + "code": "ultra.rgrab", + "scope": "ultra.rgrab", + "tableName": "campaign", + "primaryKey": "gceystozad", + "oldPayer": "ultra.rgrab", + "newPayer": "ultra.rgrab", + "oldData": "AEAyn2bsFWIAAAAAAAAAAAhVT1MAAAAA5wkAAAAAAAAAQJUGAXNz1AAAAAA=", + "newData": "AEAyn2bsFWLABoblNgAAAAhVT1MAAAAA5wkAAAAAAAAAQJUGAXNz1EbdbQ0=" + } + ], + "rlimitOps": [ + { + "operation": "OPERATION_UPDATE", + "accountUsage": { + "owner": "ultra.camp", + "netUsage": { + "lastOrdinal": 1579678322, + "valueEx": "34605", + "consumed": "689" + }, + "cpuUsage": { + "lastOrdinal": 1579678322, + "valueEx": "13185", + "consumed": "262" + }, + "ramUsage": "30214" + } + } + ], + "creationTree": [ + { + "creatorActionIndex": -1 + }, + { + "executionActionIndex": 1 + }, + { + "executionActionIndex": 2 + } + ] + } + ], + "unfilteredTransactionTraceCount": 2, + "filteredTransactionTraceCount": 1, + "unfilteredExecutedInputActionCount": 2, + "filteredExecutedInputActionCount": 1, + "unfilteredExecutedTotalActionCount": 4, + "filteredExecutedTotalActionCount": 3, + "validBlockSigningAuthorityV2": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS7rzTcsDq45H1o8BGVPQQoGCnWrsNrmqgUfLDmefVho4nvuM7Ka", + "weight": 1 + } + ] + } + }, + "activeScheduleV2": { + "version": 19, + "producers": [ + { + "accountName": "eosriobrazil", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS6e6CFvEtcCGTbj8YyVwmNRiQ7FRFjbuKfTSaJXvciaVYdW2W6B", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "cryptolions1", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS8KamrzrcGzKF7N5T8y44QwEwubHyJ7W6oeNehnmXTsEjQR8KYd", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "eosioubisoft", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS7rzTcsDq45H1o8BGVPQQoGCnWrsNrmqgUfLDmefVho4nvuM7Ka", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "uosswedenorg", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS7DjFq5FA3pyzUzXqLhA7qBSkmpZ57M6uYULTRA9E2Y4YRt7oWF", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "eosnationftw", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5uMMtMvBp4pPEBiUsfdBR9LVnJnzUonGFvQtwnh1eoXzZZKN8U", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "ivote4eosusa", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5gKJLRWxMgLWDsvKu7YaTweoQpqin5SSzJ4mDuaXnQ6tZb1F31", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "uoseouldotio", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS57zSgkDgcj1XeMFqZes5Tz6fJNnCug8KYADJ1EY2Z9YGjs7Psx", + "weight": 1 + } + ] + } + } + } + ] + }, + "filteringApplied": true, + "filteringIncludeFilterExpr": "account==\"eosio.token\" || (action==\"setabi\" \u0026\u0026 account==\"eosio\" \u0026\u0026 data.account==\"eosio.token\")" +} \ No newline at end of file diff --git a/testdata/block-224793793.pb.json b/testdata/block-224793793.pb.json new file mode 100644 index 0000000..640beb7 --- /dev/null +++ b/testdata/block-224793793.pb.json @@ -0,0 +1,1357 @@ +{ + "id": "0d6614c1b56ec48b755fc2c0c02f6657c16e75d21ceae8dd48e57dbe24d365e9", + "number": 224793793, + "version": 1, + "header": { + "timestamp": "2025-01-10T16:55:00Z", + "producer": "uoseouldotio", + "confirmed": 72, + "previous": "0d6614c0396f7ac5b8a94f560fb9717bd79a5c50130467c0e2478f69d3866eca", + "transactionMroot": "ampfmmtFjzDXLPk5yl6gB1mjRJhZ2kLGfBJBpsDnmW4=", + "actionMroot": "zXWTwzYzsq2bIMQ0k0DKciZRfLw7/w7wIdXVo/mI7Vw=", + "scheduleVersion": 19 + }, + "producerSignature": "SIG_K1_KdcEgWPMwdMg4w6ZSs4fhogSbVGe1tCedzXxDruMy6i9qWEnyaKLAAZKR59ZBHbcByYyTQqqzeQx64aaG2KWeqHj2zmyGK", + "dposProposedIrreversibleBlocknum": 224793756, + "dposIrreversibleBlocknum": 224793708, + "blockrootMerkle": { + "nodeCount": 224793792, + "activeNodes": [ + "8d0Zr1Be8DhIg/Gyzkuv1cyaHYPjDyCsf1qRDOUUupc=", + "AdNFmAHX6dK0Q3Ghe+8xwIX4rUHYEnk4q5dcqra3kfs=", + "LUwUm/LunRfqRy34IuXfyqPGKQfxEnX3tL0r9KpQXo4=", + "jjvJmggI065wTGmD/yZy48RyjoJ6tRCL57147ExLc9E=", + "3Ot5qapPH4AWvVh4jDMGyASIKR8MvhHWX3oeXT1+gJ8=", + "20gooaM/sptXvZyE0qpVbzKO3K+FVnRiWw4fxozqDg0=", + "5hrRC88/TttFtrpgM5QZZWjIgPV5ud+v+Gjz8s1rTWA=", + "YzEwPxGINdf9rgIVVsUQXSkqbZX7rofPEv9UOxtp4Ws=", + "UyioFOAVc+G2ljacf97l9R2OMKFs1PyRAbbEJEP1xGY=", + "v2lXY2h8Gp4EFhhwCS/aHJ6HMHwrp4Tzv3DTBQR0b3I=", + "6wxQEY3S/RwkSVOtbgd70qou3/KL5/sKo/LzAAjVnno=", + "MjC0UATYpItPF7FFftaoVGP/DQFOZGk3HlznCwJDzlc=" + ] + }, + "producerToLastProduced": [ + { + "name": "cryptolions1", + "lastBlockNumProduced": 224793744 + }, + { + "name": "eosioubisoft", + "lastBlockNumProduced": 224793756 + }, + { + "name": "eosnationftw", + "lastBlockNumProduced": 224793780 + }, + { + "name": "eosriobrazil", + "lastBlockNumProduced": 224793732 + }, + { + "name": "ivote4eosusa", + "lastBlockNumProduced": 224793792 + }, + { + "name": "uoseouldotio", + "lastBlockNumProduced": 224793793 + }, + { + "name": "uosswedenorg", + "lastBlockNumProduced": 224793768 + } + ], + "producerToLastImpliedIrb": [ + { + "name": "cryptolions1", + "lastBlockNumProduced": 224793696 + }, + { + "name": "eosioubisoft", + "lastBlockNumProduced": 224793708 + }, + { + "name": "eosnationftw", + "lastBlockNumProduced": 224793732 + }, + { + "name": "eosriobrazil", + "lastBlockNumProduced": 224793684 + }, + { + "name": "ivote4eosusa", + "lastBlockNumProduced": 224793744 + }, + { + "name": "uoseouldotio", + "lastBlockNumProduced": 224793744 + }, + { + "name": "uosswedenorg", + "lastBlockNumProduced": 224793720 + } + ], + "confirmCount": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 4 + ], + "pendingSchedule": { + "scheduleLibNum": 146119709, + "scheduleHash": "AGsM1+BObSU5Qsy6ZKVw0LseKGWLAS24+YspBvloWbA=", + "scheduleV2": { + "version": 19 + } + }, + "activatedProtocolFeatures": { + "protocolFeatures": [ + "DsfggBd7LAKyeNUIhhFoa0nXOZJaktm/ys1/xrdAU70=", + "D+GFuIMhUYZYxU8h6R14fymGnFvA3T/HgA95iDwUAw0=", + "GltEdlcoFG1xuRadzlKjHV+sIUO2GwCLfKkyj92LrEU=", + "GpmlnYfgbgnsWwKKnLt3SbSlrYgZAENl0C3EN5qLckE=", + "JlL1+WAGKUEJs90LveY2k/VTJK9FK3me4TeoGpBe7SU=", + "KZ3LavaSMkuJmznxbVpTCjMGKATkHwncl+nxVrRHZwc=", + "SpDADVVFTcWwWQVcohNXnG6oVpZ3EqVgF0h4hqTUzA8=", + "TnvzSNoAqUVImypoF0nrVvXeALkAAU4Tfdrjn0j2nWc=", + "T8qL2Cu9GB5xTig/g+G0XZXKWvQPuJrTl3tlPESPeMI=", + "aNyqNMBRfRlmbmszrdZzUdjF9p6ZnKHjeTG8QQopdCg=", + "hhD9I0EwDu2GzgKTqCX+51OHZNeQlH85Frjk0ANrxMQ=", + "i6Uv56OVbFzTplajF0uTHTuyq7RVeL78WfKD7NgWpAU=", + "rZ49j2UGh3Cf1o9LkLQffYJaNlsCwjpjbO+IrCrADEM=", + "rqJFf5Ud6QoTLRowexOsEnXI/CSQ0nQ53UVxU9wDr48=", + "sLrd2WfnkQuQVpYiShE0yoMsUcth2RpqIiZSqCcVxj8=", + "tUHVRFIr5UqAt+N9TvxkCLdTpxQ7oQ8XDlYCpPq4wbo=", + "tnweAOC5Pppk0GviuW23tvSufzPvZ1bV8sL+taeCqEM=", + "y3nUSfgZS/a0A+QP2jGaHi+dKyEI97bLmBZi5jk+1rk=", + "4PtksQhcxVOJcBWNBaAJwk4nb7lOGgv2pSi0j7xP9SY=", + "70MRLGVDuI2yKDouB3J4wxWuLIRxmosl8lzIhWX76pk=", + "8K9W0sWkjWCkpbXJA+37fbOnNqlO1YnQt5ffM/+dPh0=", + "/Si5iGxEafNAYqQZIy9NW4AH15C4HMxtZ1/slB59gLs=" + ] + }, + "rlimitOps": [ + { + "operation": "OPERATION_UPDATE", + "state": { + "averageBlockNetUsage": { + "lastOrdinal": 224793792, + "valueEx": "28412747", + "consumed": "37" + }, + "averageBlockCpuUsage": { + "lastOrdinal": 224793792, + "valueEx": "8739392", + "consumed": "10" + }, + "pendingNetUsage": "4024", + "pendingCpuUsage": "1531", + "totalNetWeight": "11663000000", + "totalCpuWeight": "11663000000", + "totalRamBytes": "6888864805", + "virtualNetLimit": "1048576", + "virtualCpuLimit": "400000" + } + }, + { + "operation": "OPERATION_UPDATE", + "state": { + "averageBlockNetUsage": { + "lastOrdinal": 224793793, + "valueEx": "61709308", + "consumed": "4053" + }, + "averageBlockCpuUsage": { + "lastOrdinal": 224793793, + "valueEx": "21424897", + "consumed": "1540" + }, + "totalNetWeight": "11663000000", + "totalCpuWeight": "11663000000", + "totalRamBytes": "6888864805", + "virtualNetLimit": "1048576", + "virtualCpuLimit": "400000" + } + } + ], + "filteredTransactions": [ + { + "id": "9dce5c8bee1a990d6affc11dbd7d2b473ddbea0c172ebacdff27d0b84808f4a8", + "status": "TRANSACTIONSTATUS_EXECUTED", + "cpuUsageMicroSeconds": 1316, + "netUsageWords": 432, + "packedTransaction": { + "signatures": [ + "SIG_K1_JzoKW3XEUTem78fztFyGkjGEYC6XQZSt9YqjYTYHcgL1knGd381jwfxduLUNJkKxnzqkAgmmevaNGMqzsmVS8Nj2RCiHDu" + ], + "compression": 1, + "packedContextFreeData": "eJxjAAAAAQAB", + "packedTransaction": "eJyzDWxMTxAJ2HdNhwEIWBgOLNWQCuA9zuCwZMuqnCuujA5+Msu6zx92A8mueGtk5IMsAAIcof7BYMayJhPmVwahMHEWH083VyTxb853/om5zZSEye/gRtETrmtz9iyGbRpwAZi7Ts2yRraVPSW1IL84s4R0k3YtnYriTrhJMJsYPPyixYONMEyKYYT6AC6B5iZ0kxW8/v+X8fv/n+HlElNkdQfXtaGGFAgAAJV+bf8=" + } + } + ], + "unfilteredTransactionCount": 2, + "filteredTransactionCount": 1, + "filteredTransactionTraces": [ + { + "id": "9dce5c8bee1a990d6affc11dbd7d2b473ddbea0c172ebacdff27d0b84808f4a8", + "blockNum": "224793793", + "index": "1", + "blockTime": "2025-01-10T16:55:00Z", + "producerBlockId": "0d6614c1b56ec48b755fc2c0c02f6657c16e75d21ceae8dd48e57dbe24d365e9", + "receipt": { + "status": "TRANSACTIONSTATUS_EXECUTED", + "cpuUsageMicroSeconds": 1316, + "netUsageWords": 432 + }, + "elapsed": "8147", + "netUsage": "3456", + "actionTraces": [ + { + "receiver": "swap.alcor", + "receipt": { + "receiver": "swap.alcor", + "digest": "ce14429796fc28ea5915590bc481ccfbdef7f1c2b49aebd8b6d4c7abd5ed0812", + "globalSequence": "408079743", + "authSequence": [ + { + "accountName": "cv1wz2xa3lb4", + "sequence": "73" + } + ], + "recvSequence": "36", + "codeSequence": "2", + "abiSequence": "1" + }, + "action": { + "account": "swap.alcor", + "name": "createpool", + "authorization": [ + { + "actor": "cv1wz2xa3lb4", + "permission": "active" + } + ], + "jsonData": "{\"account\":\"cv1wz2xa3lb4\",\"fee\":3000,\"sqrtPriceX64\":\"1844582586964001782\",\"tokenA\":{\"quantity\":\"0.00000000 UOS\",\"contract\":\"eosio.token\"},\"tokenB\":{\"quantity\":\"0.0000 LIFE\",\"contract\":\"eosio.token\"}}", + "rawData": "QE4cpovPw0YAAAAAAAAAAAhVT1MAAAAAAKaCNAPqMFUAAAAAAAAAAARMSUZFAAAAAKaCNAPqMFX2Q9z+FkaZGQAAAAAAAAAAuAsAAA==" + }, + "elapsed": "5323", + "transactionId": "9dce5c8bee1a990d6affc11dbd7d2b473ddbea0c172ebacdff27d0b84808f4a8", + "blockNum": "224793793", + "producerBlockId": "0d6614c1b56ec48b755fc2c0c02f6657c16e75d21ceae8dd48e57dbe24d365e9", + "blockTime": "2025-01-10T16:55:00Z", + "accountRamDeltas": [ + { + "account": "cv1wz2xa3lb4", + "delta": "702" + } + ], + "actionOrdinal": 1 + }, + { + "receiver": "swap.alcor", + "receipt": { + "receiver": "swap.alcor", + "digest": "f666d4c4c8fba0eaeeacdd81f1031ae76d8fddc2d27b7dd32a3887c4580d2702", + "globalSequence": "408079744", + "authSequence": [ + { + "accountName": "swap.alcor", + "sequence": "41" + } + ], + "recvSequence": "37", + "codeSequence": "2", + "abiSequence": "1" + }, + "action": { + "account": "swap.alcor", + "name": "logpool", + "authorization": [ + { + "actor": "swap.alcor", + "permission": "active" + } + ], + "jsonData": "{\"fee\":3000,\"feeProtocol\":0,\"poolId\":1,\"sqrtPriceX64\":\"1844582586964001782\",\"tick\":-46055,\"tickSpacing\":60,\"tokenA\":{\"quantity\":\"0.00000000 UOS\",\"contract\":\"eosio.token\"},\"tokenB\":{\"quantity\":\"0.0000 LIFE\",\"contract\":\"eosio.token\"}}", + "rawData": "AQAAAAAAAAAAAAAAAAAAAAhVT1MAAAAAAKaCNAPqMFUAAAAAAAAAAARMSUZFAAAAAKaCNAPqMFW4CwAAADwAAAD2Q9z+FkaZGQAAAAAAAAAAGUz//w==" + }, + "elapsed": "25", + "transactionId": "9dce5c8bee1a990d6affc11dbd7d2b473ddbea0c172ebacdff27d0b84808f4a8", + "blockNum": "224793793", + "producerBlockId": "0d6614c1b56ec48b755fc2c0c02f6657c16e75d21ceae8dd48e57dbe24d365e9", + "blockTime": "2025-01-10T16:55:00Z", + "actionOrdinal": 5, + "creatorActionOrdinal": 1, + "closestUnnotifiedAncestorActionOrdinal": 1, + "executionIndex": 1 + }, + { + "receiver": "eosio.token", + "receipt": { + "receiver": "eosio.token", + "digest": "60a1058ba0d815d3a7ee4d109e2e720c65dbb272a85f75a0498f401a240ace34", + "globalSequence": "408079745", + "authSequence": [ + { + "accountName": "cv1wz2xa3lb4", + "sequence": "74" + } + ], + "recvSequence": "195071", + "codeSequence": "5", + "abiSequence": "5" + }, + "action": { + "account": "eosio.token", + "name": "transfer", + "authorization": [ + { + "actor": "cv1wz2xa3lb4", + "permission": "active" + } + ], + "jsonData": "{\"from\":\"cv1wz2xa3lb4\",\"to\":\"swap.alcor\",\"quantity\":\"10.00000000 UOS\",\"memo\":\"deposit\"}", + "rawData": "QE4cpovPw0YAwKUoGlANxwDKmjsAAAAACFVPUwAAAAAHZGVwb3NpdA==" + }, + "elapsed": "1256", + "transactionId": "9dce5c8bee1a990d6affc11dbd7d2b473ddbea0c172ebacdff27d0b84808f4a8", + "blockNum": "224793793", + "producerBlockId": "0d6614c1b56ec48b755fc2c0c02f6657c16e75d21ceae8dd48e57dbe24d365e9", + "blockTime": "2025-01-10T16:55:00Z", + "actionOrdinal": 2, + "executionIndex": 2, + "filteringMatched": true + }, + { + "receiver": "cv1wz2xa3lb4", + "receipt": { + "receiver": "cv1wz2xa3lb4", + "digest": "60a1058ba0d815d3a7ee4d109e2e720c65dbb272a85f75a0498f401a240ace34", + "globalSequence": "408079746", + "authSequence": [ + { + "accountName": "cv1wz2xa3lb4", + "sequence": "75" + } + ], + "recvSequence": "28", + "codeSequence": "5", + "abiSequence": "5" + }, + "action": { + "account": "eosio.token", + "name": "transfer", + "authorization": [ + { + "actor": "cv1wz2xa3lb4", + "permission": "active" + } + ], + "jsonData": "{\"from\":\"cv1wz2xa3lb4\",\"to\":\"swap.alcor\",\"quantity\":\"10.00000000 UOS\",\"memo\":\"deposit\"}", + "rawData": "QE4cpovPw0YAwKUoGlANxwDKmjsAAAAACFVPUwAAAAAHZGVwb3NpdA==" + }, + "elapsed": "3", + "transactionId": "9dce5c8bee1a990d6affc11dbd7d2b473ddbea0c172ebacdff27d0b84808f4a8", + "blockNum": "224793793", + "producerBlockId": "0d6614c1b56ec48b755fc2c0c02f6657c16e75d21ceae8dd48e57dbe24d365e9", + "blockTime": "2025-01-10T16:55:00Z", + "actionOrdinal": 6, + "creatorActionOrdinal": 2, + "closestUnnotifiedAncestorActionOrdinal": 2, + "executionIndex": 3 + }, + { + "receiver": "swap.alcor", + "receipt": { + "receiver": "swap.alcor", + "digest": "60a1058ba0d815d3a7ee4d109e2e720c65dbb272a85f75a0498f401a240ace34", + "globalSequence": "408079747", + "authSequence": [ + { + "accountName": "cv1wz2xa3lb4", + "sequence": "76" + } + ], + "recvSequence": "38", + "codeSequence": "5", + "abiSequence": "5" + }, + "action": { + "account": "eosio.token", + "name": "transfer", + "authorization": [ + { + "actor": "cv1wz2xa3lb4", + "permission": "active" + } + ], + "jsonData": "{\"from\":\"cv1wz2xa3lb4\",\"to\":\"swap.alcor\",\"quantity\":\"10.00000000 UOS\",\"memo\":\"deposit\"}", + "rawData": "QE4cpovPw0YAwKUoGlANxwDKmjsAAAAACFVPUwAAAAAHZGVwb3NpdA==" + }, + "elapsed": "116", + "transactionId": "9dce5c8bee1a990d6affc11dbd7d2b473ddbea0c172ebacdff27d0b84808f4a8", + "blockNum": "224793793", + "producerBlockId": "0d6614c1b56ec48b755fc2c0c02f6657c16e75d21ceae8dd48e57dbe24d365e9", + "blockTime": "2025-01-10T16:55:00Z", + "accountRamDeltas": [ + { + "account": "swap.alcor", + "delta": "400" + } + ], + "actionOrdinal": 7, + "creatorActionOrdinal": 2, + "closestUnnotifiedAncestorActionOrdinal": 2, + "executionIndex": 4 + }, + { + "receiver": "eosio.token", + "receipt": { + "receiver": "eosio.token", + "digest": "9c78442f8a06499f3cb5517413ca140528bec8e1c474103b0363099d73190ab2", + "globalSequence": "408079748", + "authSequence": [ + { + "accountName": "cv1wz2xa3lb4", + "sequence": "77" + } + ], + "recvSequence": "195072", + "codeSequence": "5", + "abiSequence": "5" + }, + "action": { + "account": "eosio.token", + "name": "transfer", + "authorization": [ + { + "actor": "cv1wz2xa3lb4", + "permission": "active" + } + ], + "jsonData": "{\"from\":\"cv1wz2xa3lb4\",\"to\":\"swap.alcor\",\"quantity\":\"980.7290 LIFE\",\"memo\":\"deposit\"}", + "rawData": "QE4cpovPw0YAwKUoGlANx7qllQAAAAAABExJRkUAAAAHZGVwb3NpdA==" + }, + "elapsed": "251", + "transactionId": "9dce5c8bee1a990d6affc11dbd7d2b473ddbea0c172ebacdff27d0b84808f4a8", + "blockNum": "224793793", + "producerBlockId": "0d6614c1b56ec48b755fc2c0c02f6657c16e75d21ceae8dd48e57dbe24d365e9", + "blockTime": "2025-01-10T16:55:00Z", + "accountRamDeltas": [ + { + "account": "eosio.token", + "delta": "128" + } + ], + "actionOrdinal": 3, + "executionIndex": 5, + "filteringMatched": true + }, + { + "receiver": "cv1wz2xa3lb4", + "receipt": { + "receiver": "cv1wz2xa3lb4", + "digest": "9c78442f8a06499f3cb5517413ca140528bec8e1c474103b0363099d73190ab2", + "globalSequence": "408079749", + "authSequence": [ + { + "accountName": "cv1wz2xa3lb4", + "sequence": "78" + } + ], + "recvSequence": "29", + "codeSequence": "5", + "abiSequence": "5" + }, + "action": { + "account": "eosio.token", + "name": "transfer", + "authorization": [ + { + "actor": "cv1wz2xa3lb4", + "permission": "active" + } + ], + "jsonData": "{\"from\":\"cv1wz2xa3lb4\",\"to\":\"swap.alcor\",\"quantity\":\"980.7290 LIFE\",\"memo\":\"deposit\"}", + "rawData": "QE4cpovPw0YAwKUoGlANx7qllQAAAAAABExJRkUAAAAHZGVwb3NpdA==" + }, + "elapsed": "2", + "transactionId": "9dce5c8bee1a990d6affc11dbd7d2b473ddbea0c172ebacdff27d0b84808f4a8", + "blockNum": "224793793", + "producerBlockId": "0d6614c1b56ec48b755fc2c0c02f6657c16e75d21ceae8dd48e57dbe24d365e9", + "blockTime": "2025-01-10T16:55:00Z", + "actionOrdinal": 8, + "creatorActionOrdinal": 3, + "closestUnnotifiedAncestorActionOrdinal": 3, + "executionIndex": 6 + }, + { + "receiver": "swap.alcor", + "receipt": { + "receiver": "swap.alcor", + "digest": "9c78442f8a06499f3cb5517413ca140528bec8e1c474103b0363099d73190ab2", + "globalSequence": "408079750", + "authSequence": [ + { + "accountName": "cv1wz2xa3lb4", + "sequence": "79" + } + ], + "recvSequence": "39", + "codeSequence": "5", + "abiSequence": "5" + }, + "action": { + "account": "eosio.token", + "name": "transfer", + "authorization": [ + { + "actor": "cv1wz2xa3lb4", + "permission": "active" + } + ], + "jsonData": "{\"from\":\"cv1wz2xa3lb4\",\"to\":\"swap.alcor\",\"quantity\":\"980.7290 LIFE\",\"memo\":\"deposit\"}", + "rawData": "QE4cpovPw0YAwKUoGlANx7qllQAAAAAABExJRkUAAAAHZGVwb3NpdA==" + }, + "elapsed": "73", + "transactionId": "9dce5c8bee1a990d6affc11dbd7d2b473ddbea0c172ebacdff27d0b84808f4a8", + "blockNum": "224793793", + "producerBlockId": "0d6614c1b56ec48b755fc2c0c02f6657c16e75d21ceae8dd48e57dbe24d365e9", + "blockTime": "2025-01-10T16:55:00Z", + "accountRamDeltas": [ + { + "account": "swap.alcor", + "delta": "288" + } + ], + "actionOrdinal": 9, + "creatorActionOrdinal": 3, + "closestUnnotifiedAncestorActionOrdinal": 3, + "executionIndex": 7 + }, + { + "receiver": "eosio.token", + "receipt": { + "receiver": "eosio.token", + "digest": "81ddcb320c991ca60a8628ec69838209f593847c7e77405362af4402bbbe7ee4", + "globalSequence": "408079751", + "recvSequence": "195073", + "codeSequence": "5", + "abiSequence": "5" + }, + "action": { + "account": "eosio.token", + "name": "burn", + "jsonData": "{\"amount\":\"0.0980 LIFE\"}", + "rawData": "1AMAAAAAAAAETElGRQAAAA==" + }, + "elapsed": "5", + "transactionId": "9dce5c8bee1a990d6affc11dbd7d2b473ddbea0c172ebacdff27d0b84808f4a8", + "blockNum": "224793793", + "producerBlockId": "0d6614c1b56ec48b755fc2c0c02f6657c16e75d21ceae8dd48e57dbe24d365e9", + "blockTime": "2025-01-10T16:55:00Z", + "actionOrdinal": 10, + "creatorActionOrdinal": 3, + "closestUnnotifiedAncestorActionOrdinal": 3, + "executionIndex": 8, + "filteringMatched": true + }, + { + "receiver": "swap.alcor", + "receipt": { + "receiver": "swap.alcor", + "digest": "6e13d07b760b7ceaecb26396c06fea8337d4c35e1a6c21ec74f35d0262908812", + "globalSequence": "408079752", + "authSequence": [ + { + "accountName": "cv1wz2xa3lb4", + "sequence": "80" + } + ], + "recvSequence": "40", + "codeSequence": "2", + "abiSequence": "1" + }, + "action": { + "account": "swap.alcor", + "name": "addliquid", + "authorization": [ + { + "actor": "cv1wz2xa3lb4", + "permission": "active" + } + ], + "jsonData": "{\"deadline\":0,\"owner\":\"cv1wz2xa3lb4\",\"poolId\":1,\"tickLower\":-46560,\"tickUpper\":-45540,\"tokenADesired\":\"10.00000000 UOS\",\"tokenAMin\":\"9.00000000 UOS\",\"tokenBDesired\":\"980.7290 LIFE\",\"tokenBMin\":\"882.6561 LIFE\"}", + "rawData": "AQAAAAAAAABAThymi8/DRgDKmjsAAAAACFVPUwAAAAC6pZUAAAAAAARMSUZFAAAAIEr//xxO//8A6aQ1AAAAAAhVT1MAAAAAwa6GAAAAAAAETElGRQAAAAAAAAA=" + }, + "elapsed": "908", + "transactionId": "9dce5c8bee1a990d6affc11dbd7d2b473ddbea0c172ebacdff27d0b84808f4a8", + "blockNum": "224793793", + "producerBlockId": "0d6614c1b56ec48b755fc2c0c02f6657c16e75d21ceae8dd48e57dbe24d365e9", + "blockTime": "2025-01-10T16:55:00Z", + "accountRamDeltas": [ + { + "account": "cv1wz2xa3lb4", + "delta": "1558" + } + ], + "actionOrdinal": 4, + "executionIndex": 9 + }, + { + "receiver": "swap.alcor", + "receipt": { + "receiver": "swap.alcor", + "digest": "5859b785a8dd3db010b81596c21c6589d98467a0f64c2596a3fd0dc10c6c143e", + "globalSequence": "408079753", + "authSequence": [ + { + "accountName": "swap.alcor", + "sequence": "42" + } + ], + "recvSequence": "41", + "codeSequence": "2", + "abiSequence": "1" + }, + "action": { + "account": "swap.alcor", + "name": "logmint", + "authorization": [ + { + "actor": "swap.alcor", + "permission": "active" + } + ], + "jsonData": "{\"liquidity\":3933708051,\"owner\":\"cv1wz2xa3lb4\",\"poolId\":1,\"posId\":2,\"reserveA\":\"9.99999995 UOS\",\"reserveB\":\"980.7290 LIFE\",\"sqrtPriceX64\":\"1844582586964001782\",\"tick\":-46055,\"tickLower\":-46560,\"tickUpper\":-45540,\"tokenA\":\"9.99999995 UOS\",\"tokenB\":\"980.7290 LIFE\"}", + "rawData": "AQAAAAAAAAACAAAAAAAAAEBOHKaLz8NGIEr//xxO//8Tn3fqAAAAAPvJmjsAAAAACFVPUwAAAAC6pZUAAAAAAARMSUZFAAAA+8maOwAAAAAIVU9TAAAAALqllQAAAAAABExJRkUAAAD2Q9z+FkaZGQAAAAAAAAAAGUz//w==" + }, + "elapsed": "21", + "transactionId": "9dce5c8bee1a990d6affc11dbd7d2b473ddbea0c172ebacdff27d0b84808f4a8", + "blockNum": "224793793", + "producerBlockId": "0d6614c1b56ec48b755fc2c0c02f6657c16e75d21ceae8dd48e57dbe24d365e9", + "blockTime": "2025-01-10T16:55:00Z", + "actionOrdinal": 11, + "creatorActionOrdinal": 4, + "closestUnnotifiedAncestorActionOrdinal": 4, + "executionIndex": 10 + } + ], + "dbOps": [ + { + "operation": "OPERATION_UPDATE", + "code": "swap.alcor", + "scope": "swap.alcor", + "tableName": "system", + "oldPayer": "swap.alcor", + "newPayer": "swap.alcor", + "oldData": "AAAAAAAAAAABAQAAAAAAAAACAAAAAAAAAAAAAAAAAAAACFVPUwAAAAAApoI0A+owVQ==", + "newData": "AAAAAAAAAAABAgAAAAAAAAACAAAAAAAAAAAAAAAAAAAACFVPUwAAAAAApoI0A+owVQ==" + }, + { + "operation": "OPERATION_INSERT", + "code": "swap.alcor", + "scope": "swap.alcor", + "tableName": "pools", + "primaryKey": "............1", + "newPayer": "cv1wz2xa3lb4", + "newData": "AQAAAAAAAAABAAAAAAAAAAAIVU9TAAAAAACmgjQD6jBVAAAAAAAAAAAETElGRQAAAACmgjQD6jBVuAsAAAA8AAAAdil3n5duBAD2Q9z+FkaZGQAAAAAAAAAAGUz//+RQgWcBAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIVU9TAAAAAAAAAAAAAAAABExJRkUAAAAAAAAAAAAAAA==" + }, + { + "operation": "OPERATION_INSERT", + "code": "swap.alcor", + "scope": "............1", + "tableName": "observations", + "primaryKey": "......3bk5ci4", + "newPayer": "cv1wz2xa3lb4", + "newData": "5FCBZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + }, + { + "operation": "OPERATION_UPDATE", + "actionIndex": 2, + "code": "eosio.token", + "scope": "cv1wz2xa3lb4", + "tableName": "accounts", + "primaryKey": "........ehbp5", + "oldPayer": "ultra.eosio", + "newPayer": "ultra.eosio", + "oldData": "hX0xWiIAAAAIVU9TAAAAAA==", + "newData": "hbOWHiIAAAAIVU9TAAAAAA==" + }, + { + "operation": "OPERATION_UPDATE", + "actionIndex": 2, + "code": "eosio.token", + "scope": "swap.alcor", + "tableName": "accounts", + "primaryKey": "........ehbp5", + "oldPayer": "eosio.token", + "newPayer": "eosio.token", + "oldData": "Y2IMEgAAAAAIVU9TAAAAAA==", + "newData": "YyynTQAAAAAIVU9TAAAAAA==" + }, + { + "operation": "OPERATION_INSERT", + "actionIndex": 4, + "code": "swap.alcor", + "scope": "cv1wz2xa3lb4", + "tableName": "balances", + "newPayer": "swap.alcor", + "newData": "AAAAAAAAAAAAypo7AAAAAAhVT1MAAAAAAKaCNAPqMFU=" + }, + { + "operation": "OPERATION_UPDATE", + "actionIndex": 5, + "code": "eosio.token", + "scope": "......25ct4og", + "tableName": "stat", + "primaryKey": "......25ct4og", + "oldPayer": "eosio.token", + "newPayer": "eosio.token", + "oldData": "AIDGpH6NAwAETElGRQAAAACAxqR+jQMABExJRkUAAAAAwFSQZqCWiw==", + "newData": "LHzGpH6NAwAETElGRQAAAACAxqR+jQMABExJRkUAAAAAwFSQZqCWiw==" + }, + { + "operation": "OPERATION_UPDATE", + "actionIndex": 5, + "code": "eosio.token", + "scope": "cv1wz2xa3lb4", + "tableName": "accounts", + "primaryKey": "......25ct4og", + "oldPayer": "eosio.token", + "newPayer": "eosio.token", + "oldData": "AOH1BQAAAAAETElGRQAAAA==", + "newData": "RjtgBQAAAAAETElGRQAAAA==" + }, + { + "operation": "OPERATION_INSERT", + "actionIndex": 5, + "code": "eosio.token", + "scope": "swap.alcor", + "tableName": "accounts", + "primaryKey": "......25ct4og", + "newPayer": "eosio.token", + "newData": "5qGVAAAAAAAETElGRQAAAA==" + }, + { + "operation": "OPERATION_INSERT", + "actionIndex": 7, + "code": "swap.alcor", + "scope": "cv1wz2xa3lb4", + "tableName": "balances", + "primaryKey": "............1", + "newPayer": "swap.alcor", + "newData": "AQAAAAAAAAC6pZUAAAAAAARMSUZFAAAAAKaCNAPqMFU=" + }, + { + "operation": "OPERATION_INSERT", + "actionIndex": 9, + "code": "swap.alcor", + "scope": "............1", + "tableName": "ticks", + "primaryKey": "......bzzx52", + "newPayer": "cv1wz2xa3lb4", + "newData": "IEr//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAORQgWcA" + }, + { + "operation": "OPERATION_UPDATE", + "actionIndex": 9, + "code": "swap.alcor", + "scope": "............1", + "tableName": "ticks", + "primaryKey": "......bzzx52", + "oldPayer": "cv1wz2xa3lb4", + "newPayer": "cv1wz2xa3lb4", + "oldData": "IEr//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAORQgWcA", + "newData": "IEr//xOfd+oAAAAAE5936gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAORQgWcB" + }, + { + "operation": "OPERATION_INSERT", + "actionIndex": 9, + "code": "swap.alcor", + "scope": "............1", + "tableName": "ticks", + "primaryKey": "......bzzxb1g", + "newPayer": "cv1wz2xa3lb4", + "newData": "HE7//xOfd+oAAAAA7WCIFf////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB" + }, + { + "operation": "OPERATION_INSERT", + "actionIndex": 9, + "code": "swap.alcor", + "scope": "............1", + "tableName": "bitmaps", + "primaryKey": ".........3zzd", + "newPayer": "cv1wz2xa3lb4", + "newData": "+f8AAAAAAAAAAAAAAAAAAAAB" + }, + { + "operation": "OPERATION_INSERT", + "actionIndex": 9, + "code": "swap.alcor", + "scope": "............1", + "tableName": "bitmaps", + "primaryKey": ".........3zze", + "newPayer": "cv1wz2xa3lb4", + "newData": "+v8AAgAAAAAAAAAAAAAAAAAA" + }, + { + "operation": "OPERATION_UPDATE", + "actionIndex": 9, + "code": "swap.alcor", + "scope": "swap.alcor", + "tableName": "system", + "oldPayer": "swap.alcor", + "newPayer": "swap.alcor", + "oldData": "AAAAAAAAAAABAgAAAAAAAAACAAAAAAAAAAAAAAAAAAAACFVPUwAAAAAApoI0A+owVQ==", + "newData": "AAAAAAAAAAABAgAAAAAAAAADAAAAAAAAAAAAAAAAAAAACFVPUwAAAAAApoI0A+owVQ==" + }, + { + "operation": "OPERATION_INSERT", + "actionIndex": 9, + "code": "swap.alcor", + "scope": "............1", + "tableName": "positions", + "primaryKey": "............2", + "newPayer": "cv1wz2xa3lb4", + "newData": "AgAAAAAAAABAThymi8/DRiBK//8cTv//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + }, + { + "operation": "OPERATION_UPDATE", + "actionIndex": 9, + "code": "swap.alcor", + "scope": "............1", + "tableName": "positions", + "primaryKey": "............2", + "oldPayer": "cv1wz2xa3lb4", + "newPayer": "cv1wz2xa3lb4", + "oldData": "AgAAAAAAAABAThymi8/DRiBK//8cTv//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "newData": "AgAAAAAAAABAThymi8/DRiBK//8cTv//E5936gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + }, + { + "operation": "OPERATION_UPDATE", + "actionIndex": 9, + "code": "swap.alcor", + "scope": "swap.alcor", + "tableName": "pools", + "primaryKey": "............1", + "oldPayer": "cv1wz2xa3lb4", + "newPayer": "cv1wz2xa3lb4", + "oldData": "AQAAAAAAAAABAAAAAAAAAAAIVU9TAAAAAACmgjQD6jBVAAAAAAAAAAAETElGRQAAAACmgjQD6jBVuAsAAAA8AAAAdil3n5duBAD2Q9z+FkaZGQAAAAAAAAAAGUz//+RQgWcBAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIVU9TAAAAAAAAAAAAAAAABExJRkUAAAAAAAAAAAAAAA==", + "newData": "AQAAAAAAAAABAAAAAAAAAAAIVU9TAAAAAACmgjQD6jBVAAAAAAAAAAAETElGRQAAAACmgjQD6jBVuAsAAAA8AAAAdil3n5duBAD2Q9z+FkaZGQAAAAAAAAAAGUz//+RQgWcBAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIVU9TAAAAAAAAAAAAAAAABExJRkUAAAATn3fqAAAAAA==" + }, + { + "operation": "OPERATION_UPDATE", + "actionIndex": 9, + "code": "swap.alcor", + "scope": "cv1wz2xa3lb4", + "tableName": "balances", + "oldPayer": "swap.alcor", + "newPayer": "swap.alcor", + "oldData": "AAAAAAAAAAAAypo7AAAAAAhVT1MAAAAAAKaCNAPqMFU=", + "newData": "AAAAAAAAAAAFAAAAAAAAAAhVT1MAAAAAAKaCNAPqMFU=" + }, + { + "operation": "OPERATION_UPDATE", + "actionIndex": 9, + "code": "swap.alcor", + "scope": "cv1wz2xa3lb4", + "tableName": "balances", + "primaryKey": "............1", + "oldPayer": "swap.alcor", + "newPayer": "swap.alcor", + "oldData": "AQAAAAAAAAC6pZUAAAAAAARMSUZFAAAAAKaCNAPqMFU=", + "newData": "AQAAAAAAAAAAAAAAAAAAAARMSUZFAAAAAKaCNAPqMFU=" + }, + { + "operation": "OPERATION_UPDATE", + "actionIndex": 9, + "code": "swap.alcor", + "scope": "swap.alcor", + "tableName": "pools", + "primaryKey": "............1", + "oldPayer": "cv1wz2xa3lb4", + "newPayer": "cv1wz2xa3lb4", + "oldData": "AQAAAAAAAAABAAAAAAAAAAAIVU9TAAAAAACmgjQD6jBVAAAAAAAAAAAETElGRQAAAACmgjQD6jBVuAsAAAA8AAAAdil3n5duBAD2Q9z+FkaZGQAAAAAAAAAAGUz//+RQgWcBAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIVU9TAAAAAAAAAAAAAAAABExJRkUAAAATn3fqAAAAAA==", + "newData": "AQAAAAAAAAAB+8maOwAAAAAIVU9TAAAAAACmgjQD6jBVuqWVAAAAAAAETElGRQAAAACmgjQD6jBVuAsAAAA8AAAAdil3n5duBAD2Q9z+FkaZGQAAAAAAAAAAGUz//+RQgWcBAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIVU9TAAAAAAAAAAAAAAAABExJRkUAAAATn3fqAAAAAA==" + } + ], + "ramOps": [ + { + "operation": "OPERATION_PRIMARY_INDEX_ADD", + "payer": "cv1wz2xa3lb4", + "delta": "290", + "usage": "902", + "namespace": "NAMESPACE_TABLE_ROW", + "uniqueKey": "swap.alcor:swap.alcor:pools:............1", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_SECONDARY_INDEX_ADD", + "payer": "cv1wz2xa3lb4", + "delta": "160", + "usage": "1062", + "namespace": "NAMESPACE_SECONDARY_INDEX", + "uniqueKey": "swap.alcor:swap.alcor:pools:............1", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "payer": "cv1wz2xa3lb4", + "delta": "112", + "usage": "1174", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "swap.alcor:............1:observations", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_PRIMARY_INDEX_ADD", + "payer": "cv1wz2xa3lb4", + "delta": "140", + "usage": "1314", + "namespace": "NAMESPACE_TABLE_ROW", + "uniqueKey": "swap.alcor:............1:observations:......3bk5ci4", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "actionIndex": 4, + "payer": "swap.alcor", + "delta": "112", + "usage": "2415398", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "swap.alcor:cv1wz2xa3lb4:balances", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_PRIMARY_INDEX_ADD", + "actionIndex": 4, + "payer": "swap.alcor", + "delta": "144", + "usage": "2415542", + "namespace": "NAMESPACE_TABLE_ROW", + "uniqueKey": "swap.alcor:cv1wz2xa3lb4:balances:", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_SECONDARY_INDEX_ADD", + "actionIndex": 4, + "payer": "swap.alcor", + "delta": "144", + "usage": "2415686", + "namespace": "NAMESPACE_SECONDARY_INDEX", + "uniqueKey": "swap.alcor:cv1wz2xa3lb4:balances:", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_PRIMARY_INDEX_ADD", + "actionIndex": 5, + "payer": "eosio.token", + "delta": "128", + "usage": "352035", + "namespace": "NAMESPACE_TABLE_ROW", + "uniqueKey": "eosio.token:swap.alcor:accounts:......25ct4og", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_PRIMARY_INDEX_ADD", + "actionIndex": 7, + "payer": "swap.alcor", + "delta": "144", + "usage": "2415830", + "namespace": "NAMESPACE_TABLE_ROW", + "uniqueKey": "swap.alcor:cv1wz2xa3lb4:balances:............1", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_SECONDARY_INDEX_ADD", + "actionIndex": 7, + "payer": "swap.alcor", + "delta": "144", + "usage": "2415974", + "namespace": "NAMESPACE_SECONDARY_INDEX", + "uniqueKey": "swap.alcor:cv1wz2xa3lb4:balances:............1", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "actionIndex": 9, + "payer": "cv1wz2xa3lb4", + "delta": "112", + "usage": "1426", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "swap.alcor:............1:ticks", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_PRIMARY_INDEX_ADD", + "actionIndex": 9, + "payer": "cv1wz2xa3lb4", + "delta": "193", + "usage": "1619", + "namespace": "NAMESPACE_TABLE_ROW", + "uniqueKey": "swap.alcor:............1:ticks:......bzzx52", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_PRIMARY_INDEX_ADD", + "actionIndex": 9, + "payer": "cv1wz2xa3lb4", + "delta": "193", + "usage": "1812", + "namespace": "NAMESPACE_TABLE_ROW", + "uniqueKey": "swap.alcor:............1:ticks:......bzzxb1g", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "actionIndex": 9, + "payer": "cv1wz2xa3lb4", + "delta": "112", + "usage": "1924", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "swap.alcor:............1:bitmaps", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_PRIMARY_INDEX_ADD", + "actionIndex": 9, + "payer": "cv1wz2xa3lb4", + "delta": "130", + "usage": "2054", + "namespace": "NAMESPACE_TABLE_ROW", + "uniqueKey": "swap.alcor:............1:bitmaps:.........3zzd", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_PRIMARY_INDEX_ADD", + "actionIndex": 9, + "payer": "cv1wz2xa3lb4", + "delta": "130", + "usage": "2184", + "namespace": "NAMESPACE_TABLE_ROW", + "uniqueKey": "swap.alcor:............1:bitmaps:.........3zze", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "actionIndex": 9, + "payer": "cv1wz2xa3lb4", + "delta": "112", + "usage": "2296", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "swap.alcor:............1:positions", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_PRIMARY_INDEX_ADD", + "actionIndex": 9, + "payer": "cv1wz2xa3lb4", + "delta": "192", + "usage": "2488", + "namespace": "NAMESPACE_TABLE_ROW", + "uniqueKey": "swap.alcor:............1:positions:............2", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_SECONDARY_INDEX_ADD", + "actionIndex": 9, + "payer": "cv1wz2xa3lb4", + "delta": "144", + "usage": "2632", + "namespace": "NAMESPACE_SECONDARY_INDEX", + "uniqueKey": "swap.alcor:............1:positions:............2", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_CREATE_TABLE", + "actionIndex": 9, + "payer": "cv1wz2xa3lb4", + "delta": "112", + "usage": "2744", + "namespace": "NAMESPACE_TABLE", + "uniqueKey": "swap.alcor:............1:positions...1", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_SECONDARY_INDEX_ADD", + "actionIndex": 9, + "payer": "cv1wz2xa3lb4", + "delta": "128", + "usage": "2872", + "namespace": "NAMESPACE_SECONDARY_INDEX", + "uniqueKey": "swap.alcor:............1:positions...1:............2", + "action": "ACTION_ADD" + } + ], + "rlimitOps": [ + { + "operation": "OPERATION_UPDATE", + "accountUsage": { + "owner": "cv1wz2xa3lb4", + "netUsage": { + "lastOrdinal": 1579686600, + "valueEx": "25647", + "consumed": "3457" + }, + "cpuUsage": { + "lastOrdinal": 1579686600, + "valueEx": "9757", + "consumed": "1317" + }, + "ramUsage": "2872" + } + } + ], + "tableOps": [ + { + "operation": "OPERATION_INSERT", + "payer": "cv1wz2xa3lb4", + "code": "swap.alcor", + "scope": "............1", + "tableName": "observations" + }, + { + "operation": "OPERATION_INSERT", + "actionIndex": 4, + "payer": "swap.alcor", + "code": "swap.alcor", + "scope": "cv1wz2xa3lb4", + "tableName": "balances" + }, + { + "operation": "OPERATION_INSERT", + "actionIndex": 9, + "payer": "cv1wz2xa3lb4", + "code": "swap.alcor", + "scope": "............1", + "tableName": "ticks" + }, + { + "operation": "OPERATION_INSERT", + "actionIndex": 9, + "payer": "cv1wz2xa3lb4", + "code": "swap.alcor", + "scope": "............1", + "tableName": "bitmaps" + }, + { + "operation": "OPERATION_INSERT", + "actionIndex": 9, + "payer": "cv1wz2xa3lb4", + "code": "swap.alcor", + "scope": "............1", + "tableName": "positions" + }, + { + "operation": "OPERATION_INSERT", + "actionIndex": 9, + "payer": "cv1wz2xa3lb4", + "code": "swap.alcor", + "scope": "............1", + "tableName": "positions...1" + } + ], + "creationTree": [ + { + "creatorActionIndex": -1 + }, + { + "executionActionIndex": 1 + }, + { + "creatorActionIndex": -1, + "executionActionIndex": 2 + }, + { + "creatorActionIndex": 2, + "executionActionIndex": 3 + }, + { + "creatorActionIndex": 2, + "executionActionIndex": 4 + }, + { + "creatorActionIndex": -1, + "executionActionIndex": 5 + }, + { + "creatorActionIndex": 5, + "executionActionIndex": 6 + }, + { + "creatorActionIndex": 5, + "executionActionIndex": 7 + }, + { + "creatorActionIndex": 5, + "executionActionIndex": 8 + }, + { + "creatorActionIndex": -1, + "executionActionIndex": 9 + }, + { + "creatorActionIndex": 9, + "executionActionIndex": 10 + } + ] + } + ], + "unfilteredTransactionTraceCount": 3, + "filteredTransactionTraceCount": 1, + "unfilteredExecutedInputActionCount": 7, + "filteredExecutedInputActionCount": 2, + "unfilteredExecutedTotalActionCount": 14, + "filteredExecutedTotalActionCount": 3, + "validBlockSigningAuthorityV2": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS57zSgkDgcj1XeMFqZes5Tz6fJNnCug8KYADJ1EY2Z9YGjs7Psx", + "weight": 1 + } + ] + } + }, + "activeScheduleV2": { + "version": 19, + "producers": [ + { + "accountName": "eosriobrazil", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS6e6CFvEtcCGTbj8YyVwmNRiQ7FRFjbuKfTSaJXvciaVYdW2W6B", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "cryptolions1", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS8KamrzrcGzKF7N5T8y44QwEwubHyJ7W6oeNehnmXTsEjQR8KYd", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "eosioubisoft", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS7rzTcsDq45H1o8BGVPQQoGCnWrsNrmqgUfLDmefVho4nvuM7Ka", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "uosswedenorg", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS7DjFq5FA3pyzUzXqLhA7qBSkmpZ57M6uYULTRA9E2Y4YRt7oWF", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "eosnationftw", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5uMMtMvBp4pPEBiUsfdBR9LVnJnzUonGFvQtwnh1eoXzZZKN8U", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "ivote4eosusa", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5gKJLRWxMgLWDsvKu7YaTweoQpqin5SSzJ4mDuaXnQ6tZb1F31", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "uoseouldotio", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS57zSgkDgcj1XeMFqZes5Tz6fJNnCug8KYADJ1EY2Z9YGjs7Psx", + "weight": 1 + } + ] + } + } + } + ] + }, + "filteringApplied": true, + "filteringIncludeFilterExpr": "(account==\"eosio.token\" \u0026\u0026 receiver==\"eosio.token\") || (action==\"setabi\" \u0026\u0026 account==\"eosio\" \u0026\u0026 data.account==\"eosio.token\")" +} \ No newline at end of file diff --git a/testdata/block-30080030.json b/testdata/block-30080030.json new file mode 100644 index 0000000..9653de6 --- /dev/null +++ b/testdata/block-30080030.json @@ -0,0 +1,169 @@ +{ + "id": "01cafc1e3ef76abd487a5ee96a402eec4fd3b805c3b02a7baa7778fe589f6290", + "number": 30080030, + "version": 1, + "header": { + "timestamp": { + "seconds": 1637144178 + }, + "producer": "eosriobrazil", + "confirmed": 72, + "previous": "01cafc1dd15b6e03f926333fd6d012de1b96e0142ac6d132d78db1d782372ba1", + "transaction_mroot": "TPhI/nrVNzKI2dm3+IoLj1L9NxR1LSJS5OC89B7EwHY=", + "action_mroot": "6Mpw81zMM4P7xWRymVqvYwel0au71f2UgIHTTBuu4+A=", + "schedule_version": 19 + }, + "producer_signature": "SIG_K1_KfyHj7ou2yMkUoYSH7stuoWQG68rsb7vfQGLj8ir1SQU6pkHJtxk4Wj6VpLhTPVqxFBaffyLmj2CicWrHGHdMNzzvHj2mG", + "dpos_proposed_irreversible_blocknum": 30079993, + "dpos_irreversible_blocknum": 30079945, + "blockroot_merkle": { + "node_count": 30080029, + "active_nodes": [ + "Acr8HdFbbgP5JjM/1tAS3huW4BQqxtEy142x14I3K6E=", + "+n9yYJumXlsEqdL7kuJxPTrZRfuYvvNZgNSbuyhw2Lc=", + "OpiQ8KXUrIgjP86scNH8IygOqD7FYwWU8xw38cWYrME=", + "xdYHoTuV6A6STjyoNtxkjoBxdquaVwzRJibnuIIlAxo=", + "sybZloyK3b/MyWGJwbvpa4ZGMjzj9xuPQ4YeCuExybQ=", + "2Fq2XqQhK1QQ3BYeaLAwSqOXFwAZHZ+MPXbdBy+VQes=", + "E5mYZa4QcZWXmQKgZ9Nfj8h6WK2emhQSIPmjpl0/J+4=", + "bU7Hp5Clczb+2iZ43y/MNUyAcm9WBPrITbXIvP6ZCvk=", + "fmHUhwyG+QzFNoGLYpU0dHNbnE1vWQKUB1ZL3Tq6cv8=", + "BSgs3B/ycD73kBSdsjMpTaelJSKkRVq3WruTLMKPZgM=", + "5t5YnpP4qmBNWCbX6V7chfGE5LtzwLKXmQoRs/8/dG4=", + "kUO9gxUKvOkmXX3lxxgPOoYq24A+xUXCda28uihIcY0=", + "ZyuguWpqI2srBgHqHCycqe/LFj0Z+Mw277yB4Zgb1cU=", + "9perNej1SMCrj0jzwPX0j5GnkZT0KzClFZKHNjqjukQ=", + "uQG9Lr2ty/cNoUbiWnELPU37K+VSIXUOIzaUBDiJzcc=", + "5/7pcugajox4Ly7BRn7AO0lAe81n5qVK9btZo5T0QxE=" + ] + }, + "producer_to_last_produced": [ + { + "name": "cryptolions1", + "last_block_num_produced": 30079969 + }, + { + "name": "eosnationftw", + "last_block_num_produced": 30080005 + }, + { + "name": "eosriobrazil", + "last_block_num_produced": 30080030 + }, + { + "name": "eosubisoft1", + "last_block_num_produced": 30079981 + }, + { + "name": "ivote4eosusa", + "last_block_num_produced": 30080017 + }, + { + "name": "uoseouldotio", + "last_block_num_produced": 30080029 + }, + { + "name": "uosswedenorg", + "last_block_num_produced": 30079993 + } + ], + "producer_to_last_implied_irb": [ + { + "name": "cryptolions1", + "last_block_num_produced": 30079921 + }, + { + "name": "eosnationftw", + "last_block_num_produced": 30079957 + }, + { + "name": "eosriobrazil", + "last_block_num_produced": 30079981 + }, + { + "name": "eosubisoft1", + "last_block_num_produced": 30079933 + }, + { + "name": "ivote4eosusa", + "last_block_num_produced": 30079969 + }, + { + "name": "uoseouldotio", + "last_block_num_produced": 30079981 + }, + { + "name": "uosswedenorg", + "last_block_num_produced": 30079945 + } + ], + "confirm_count": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 4 + ], + "pending_schedule": { + "schedule_lib_num": 16618326, + "schedule_hash": "GEL0mDan2WfG10pHQu8S5BApLivbzHDHO3TvmSZahio=", + "schedule_v2": { + "version": 19 + } + }, + "activated_protocol_features": { + "protocol_features": [ + "DsfggBd7LAKyeNUIhhFoa0nXOZJaktm/ys1/xrdAU70=", + "GpmlnYfgbgnsWwKKnLt3SbSlrYgZAENl0C3EN5qLckE=", + "JlL1+WAGKUEJs90LveY2k/VTJK9FK3me4TeoGpBe7SU=", + "KZ3LavaSMkuJmznxbVpTCjMGKATkHwncl+nxVrRHZwc=", + "SpDADVVFTcWwWQVcohNXnG6oVpZ3EqVgF0h4hqTUzA8=", + "TnvzSNoAqUVImypoF0nrVvXeALkAAU4Tfdrjn0j2nWc=", + "T8qL2Cu9GB5xTig/g+G0XZXKWvQPuJrTl3tlPESPeMI=", + "aNyqNMBRfRlmbmszrdZzUdjF9p6ZnKHjeTG8QQopdCg=", + "i6Uv56OVbFzTplajF0uTHTuyq7RVeL78WfKD7NgWpAU=", + "rZ49j2UGh3Cf1o9LkLQffYJaNlsCwjpjbO+IrCrADEM=", + "4PtksQhcxVOJcBWNBaAJwk4nb7lOGgv2pSi0j7xP9SY=", + "70MRLGVDuI2yKDouB3J4wxWuLIRxmosl8lzIhWX76pk=", + "8K9W0sWkjWCkpbXJA+37fbOnNqlO1YnQt5ffM/+dPh0=" + ] + }, + "unfiltered_transaction_count": 4, + "unfiltered_transaction_trace_count": 5, + "unfiltered_executed_input_action_count": 5, + "unfiltered_executed_total_action_count": 5, + "filtering_applied": true, + "filtering_include_filter_expr": "executed \u0026\u0026 action==\"create\" \u0026\u0026 account==\"eosio.nft.ft\" \u0026\u0026 receiver==\"eosio.nft.ft\"" +} \ No newline at end of file diff --git a/testdata/block-30080032-expected.json b/testdata/block-30080032-expected.json new file mode 100644 index 0000000..699c729 --- /dev/null +++ b/testdata/block-30080032-expected.json @@ -0,0 +1,131 @@ +{ + "topic": "", + "headers": [ + "ce_source", + "dkafka-test", + "ce_id", + "Re8M2gsHU9rTrPn4rV3jyL/DKdenePj37KA74BZMFIYShQo5Xl/OdHQ8lyqnjWYCopxiNaZZ8oQzr9nBTU8B4w==", + "ce_type", + "TestType", + "ce_time", + "2021-11-17T10:16:19Z", + "ce_blkstep", + "NEW" + ], + "partition": 0, + "offset": 0, + "ts": 0, + "key": "a2a53dce154c2ccdca52a981318775938de02f7efef88926ae1d7fd992988530", + "payload": { + "block_num": 30080032, + "block_id": "01cafc203bf4bf807266fe5ac12c4b9ef72bd6482367a6c95bff70161dbeb462", + "status": "EXECUTED", + "executed": true, + "block_step": "NEW", + "trx_id": "a2a53dce154c2ccdca52a981318775938de02f7efef88926ae1d7fd992988530", + "act_info": { + "account": "eosio.nft.ft", + "receiver": "eosio.nft.ft", + "action": "create", + "global_seq": 80493723, + "authorizations": [ + "ultra.nft.ft@backend" + ], + "db_ops": [ + { + "operation": 2, + "code": "eosio.nft.ft", + "table_name": "next.factory", + "primary_key": "next.factory", + "old_payer": "eosio.nft.ft", + "new_payer": "eosio.nft.ft", + "old_data": "GgAAAAAAAAA=", + "new_data": "GwAAAAAAAAA=", + "new_json": { + "value": 27 + }, + "old_json": { + "value": 26 + } + }, + { + "operation": 1, + "code": "eosio.nft.ft", + "scope": "eosio.nft.ft", + "table_name": "factory.a", + "primary_key": "...........1e", + "new_payer": "ultra.nft.ft", + "new_data": "GgAAAAAAAACQF8hrAnNz1JAXyGsCc3PUAAAAAAAAAAAAAAAAAAAAAAAIVVNEAAAAAAJAKB5JiUMD1PQBQNIeron8Qtz0AQAAAAABAAAAAAABAAAAAAGQF8hrAnNz1AABfGh0dHBzOi8vczMudXMtZWFzdC0xLndhc2FiaXN5cy5jb20vdWx0cmFpby11bmlxLXN0YWdpbmcvZDZjOTk5YmJiZDVmM2MxMjJkMjI2MWUyYjc4N2FjYjNlMDNhYWM2YTE4NTIwMjRiOTY1NmM4NWQ3YTk5ZmFiZC56aXDWyZm7vV88Ei0iYeK3h6yz4DqsahhSAkuWVshdepn6vQEoAAAAAAAAAAAAAAA=", + "new_json": { + "asset_creator": "ultra.nft.ft", + "asset_manager": "ultra.nft.ft", + "chosen_rate": [], + "conditionless_receivers": [ + "ultra.nft.ft" + ], + "conversion_rate_oracle_contract": "", + "existing_tokens_no": 0, + "id": 26, + "lockup_time": 0, + "max_mintable_tokens": 40, + "meta_hash": "d6c999bbbd5f3c122d2261e2b787acb3e03aac6a1852024b9656c85d7a99fabd", + "meta_uris": [ + "https://s3.us-east-1.wasabisys.com/ultraio-uniq-staging/d6c999bbbd5f3c122d2261e2b787acb3e03aac6a1852024b9656c85d7a99fabd.zip" + ], + "minimum_resell_price": {"amount":0,"precision":8,"symbol":"USD"}, + "minted_tokens_no": 0, + "recall_window_start": 0, + "resale_shares": [ + { + "basis_point": 500, + "receiver": "uk1ob2ed3so4" + }, + { + "basis_point": 500, + "receiver": "vl1jt2hi3vd4" + } + ], + "stat": 0 + } + } + ], + "json_data": { + "create": { + "asset_creator": "ultra.nft.ft", + "asset_manager": "ultra.nft.ft", + "chosen_rate": null, + "conditionless_receivers": [ + "ultra.nft.ft" + ], + "conversion_rate_oracle_contract": null, + "lockup_time": 0, + "max_mintable_tokens": 40, + "memo": "b0b70c2f-5895-42b7-bfbf-0992bd815271", + "meta_hash": "d6c999bbbd5f3c122d2261e2b787acb3e03aac6a1852024b9656c85d7a99fabd", + "meta_uris": [ + "https://s3.us-east-1.wasabisys.com/ultraio-uniq-staging/d6c999bbbd5f3c122d2261e2b787acb3e03aac6a1852024b9656c85d7a99fabd.zip" + ], + "minimum_resell_price": "0.00000000 USD", + "mintable_window_end": null, + "mintable_window_start": null, + "recall_window_end": null, + "recall_window_start": 0, + "resale_shares": [ + { + "basis_point": 500, + "receiver": "uk1ob2ed3so4" + }, + { + "basis_point": 500, + "receiver": "vl1jt2hi3vd4" + } + ], + "stat": null, + "trading_window_end": null, + "trading_window_start": null, + "version": 0 + } + } + } + } +} \ No newline at end of file diff --git a/testdata/block-30080032.json b/testdata/block-30080032.json new file mode 100644 index 0000000..509bb9d --- /dev/null +++ b/testdata/block-30080032.json @@ -0,0 +1,291 @@ +{ + "id": "01cafc203bf4bf807266fe5ac12c4b9ef72bd6482367a6c95bff70161dbeb462", + "number": 30080032, + "version": 1, + "header": { + "timestamp": { + "seconds": 1637144179 + }, + "producer": "eosriobrazil", + "previous": "01cafc1ff68d18474c5f981049835c9296eb60a7cedcea35c730ff5e4df767f1", + "transaction_mroot": "sIW7TOzBD7qeDI7OmTx21VOF9ok3IaR+h8l7L7P9FzA=", + "action_mroot": "Ax+VcM0JJ4LVacuYuxI+qgioPkxQRj35AUD93QRRNyo=", + "schedule_version": 19 + }, + "producer_signature": "SIG_K1_JwJAQQnHPFxKfDTnqKf6k2atXWvK6zoP3YaDh5k6EAiTCNZb2oS5PMfRN4Gogv6Vm5yyZBGqtjaBgSe4P35mpwV8cmPJrH", + "dpos_proposed_irreversible_blocknum": 30079993, + "dpos_irreversible_blocknum": 30079945, + "blockroot_merkle": { + "node_count": 30080031, + "active_nodes": [ + "Acr8H/aNGEdMX5gQSYNckpbrYKfO3Oo1xzD/Xk33Z/E=", + "FdWNO100ip6hctXNdU5b2IYqm3sKfrOerh0UgrpH07g=", + "+n9yYJumXlsEqdL7kuJxPTrZRfuYvvNZgNSbuyhw2Lc=", + "OpiQ8KXUrIgjP86scNH8IygOqD7FYwWU8xw38cWYrME=", + "xdYHoTuV6A6STjyoNtxkjoBxdquaVwzRJibnuIIlAxo=", + "sybZloyK3b/MyWGJwbvpa4ZGMjzj9xuPQ4YeCuExybQ=", + "2Fq2XqQhK1QQ3BYeaLAwSqOXFwAZHZ+MPXbdBy+VQes=", + "E5mYZa4QcZWXmQKgZ9Nfj8h6WK2emhQSIPmjpl0/J+4=", + "bU7Hp5Clczb+2iZ43y/MNUyAcm9WBPrITbXIvP6ZCvk=", + "fmHUhwyG+QzFNoGLYpU0dHNbnE1vWQKUB1ZL3Tq6cv8=", + "BSgs3B/ycD73kBSdsjMpTaelJSKkRVq3WruTLMKPZgM=", + "5t5YnpP4qmBNWCbX6V7chfGE5LtzwLKXmQoRs/8/dG4=", + "kUO9gxUKvOkmXX3lxxgPOoYq24A+xUXCda28uihIcY0=", + "ZyuguWpqI2srBgHqHCycqe/LFj0Z+Mw277yB4Zgb1cU=", + "9perNej1SMCrj0jzwPX0j5GnkZT0KzClFZKHNjqjukQ=", + "uQG9Lr2ty/cNoUbiWnELPU37K+VSIXUOIzaUBDiJzcc=", + "xHVzY5izAkmU1wNyJciNGrm5d5C191R+wEsqAQYrbjQ=" + ] + }, + "producer_to_last_produced": [ + { + "name": "cryptolions1", + "last_block_num_produced": 30079969 + }, + { + "name": "eosnationftw", + "last_block_num_produced": 30080005 + }, + { + "name": "eosriobrazil", + "last_block_num_produced": 30080032 + }, + { + "name": "eosubisoft1", + "last_block_num_produced": 30079981 + }, + { + "name": "ivote4eosusa", + "last_block_num_produced": 30080017 + }, + { + "name": "uoseouldotio", + "last_block_num_produced": 30080029 + }, + { + "name": "uosswedenorg", + "last_block_num_produced": 30079993 + } + ], + "producer_to_last_implied_irb": [ + { + "name": "cryptolions1", + "last_block_num_produced": 30079921 + }, + { + "name": "eosnationftw", + "last_block_num_produced": 30079957 + }, + { + "name": "eosriobrazil", + "last_block_num_produced": 30079993 + }, + { + "name": "eosubisoft1", + "last_block_num_produced": 30079933 + }, + { + "name": "ivote4eosusa", + "last_block_num_produced": 30079969 + }, + { + "name": "uoseouldotio", + "last_block_num_produced": 30079981 + }, + { + "name": "uosswedenorg", + "last_block_num_produced": 30079945 + } + ], + "confirm_count": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 4, + 4, + 4 + ], + "pending_schedule": { + "schedule_lib_num": 16618326, + "schedule_hash": "GEL0mDan2WfG10pHQu8S5BApLivbzHDHO3TvmSZahio=", + "schedule_v2": { + "version": 19 + } + }, + "activated_protocol_features": { + "protocol_features": [ + "DsfggBd7LAKyeNUIhhFoa0nXOZJaktm/ys1/xrdAU70=", + "GpmlnYfgbgnsWwKKnLt3SbSlrYgZAENl0C3EN5qLckE=", + "JlL1+WAGKUEJs90LveY2k/VTJK9FK3me4TeoGpBe7SU=", + "KZ3LavaSMkuJmznxbVpTCjMGKATkHwncl+nxVrRHZwc=", + "SpDADVVFTcWwWQVcohNXnG6oVpZ3EqVgF0h4hqTUzA8=", + "TnvzSNoAqUVImypoF0nrVvXeALkAAU4Tfdrjn0j2nWc=", + "T8qL2Cu9GB5xTig/g+G0XZXKWvQPuJrTl3tlPESPeMI=", + "aNyqNMBRfRlmbmszrdZzUdjF9p6ZnKHjeTG8QQopdCg=", + "i6Uv56OVbFzTplajF0uTHTuyq7RVeL78WfKD7NgWpAU=", + "rZ49j2UGh3Cf1o9LkLQffYJaNlsCwjpjbO+IrCrADEM=", + "4PtksQhcxVOJcBWNBaAJwk4nb7lOGgv2pSi0j7xP9SY=", + "70MRLGVDuI2yKDouB3J4wxWuLIRxmosl8lzIhWX76pk=", + "8K9W0sWkjWCkpbXJA+37fbOnNqlO1YnQt5ffM/+dPh0=" + ] + }, + "filtered_transactions": [ + { + "id": "a2a53dce154c2ccdca52a981318775938de02f7efef88926ae1d7fd992988530", + "index": 2, + "status": 1, + "cpu_usage_micro_seconds": 148, + "net_usage_words": 49, + "packed_transaction": { + "signatures": [ + "SIG_K1_K92Mx5xMQ92FyrjWZ79ZgcQ1KJAGMmK6ZX51vSP9rNsL7BjMQxL4QZxBuwxeBKUENJPjAqmQZwTtfe9eJJFnsJEGAeCytZ" + ], + "packed_transaction": "59aUYRj8Kf6jvQAAAAABkBfIawLqMFUAAAAAqGzURQGQF8hrAnNz1AAAACBNBZE5qQIBJGIwYjcwYzJmLTU4OTUtNDJiNy1iZmJmLTA5OTJiZDgxNTI3MQEAAAAAAAAAAAGQF8hrAnNz1AGQF8hrAnNz1AAAAQAAAAAAAAAACFVTRAAAAAABAkAoHkmJQwPU9AFA0h6uifxC3PQBAAAAAAEAAAAAAAEoAAAAAQAAAAABAZAXyGsCc3PUAAEBfGh0dHBzOi8vczMudXMtZWFzdC0xLndhc2FiaXN5cy5jb20vdWx0cmFpby11bmlxLXN0YWdpbmcvZDZjOTk5YmJiZDVmM2MxMjJkMjI2MWUyYjc4N2FjYjNlMDNhYWM2YTE4NTIwMjRiOTY1NmM4NWQ3YTk5ZmFiZC56aXAB1smZu71fPBItImHit4ess+A6rGoYUgJLllbIXXqZ+r0A" + } + } + ], + "unfiltered_transaction_count": 5, + "filtered_transaction_count": 1, + "filtered_transaction_traces": [ + { + "id": "a2a53dce154c2ccdca52a981318775938de02f7efef88926ae1d7fd992988530", + "block_num": 30080032, + "index": 3, + "block_time": { + "seconds": 1637144179 + }, + "producer_block_id": "01cafc203bf4bf807266fe5ac12c4b9ef72bd6482367a6c95bff70161dbeb462", + "receipt": { + "status": 1, + "cpu_usage_micro_seconds": 148, + "net_usage_words": 49 + }, + "elapsed": 164, + "net_usage": 392, + "action_traces": [ + { + "receiver": "eosio.nft.ft", + "receipt": { + "receiver": "eosio.nft.ft", + "digest": "2f73e4f4b3b676708f1388855e1b1ce511d4938af063efe0a01cf127b589f84e", + "global_sequence": 80493723, + "auth_sequence": [ + { + "account_name": "ultra.nft.ft", + "sequence": 54 + } + ], + "recv_sequence": 46, + "code_sequence": 5, + "abi_sequence": 5 + }, + "action": { + "account": "eosio.nft.ft", + "name": "create", + "authorization": [ + { + "actor": "ultra.nft.ft", + "permission": "backend" + } + ], + "json_data": "{\"create\":{\"asset_creator\":\"ultra.nft.ft\",\"asset_manager\":\"ultra.nft.ft\",\"chosen_rate\":null,\"conditionless_receivers\":[\"ultra.nft.ft\"],\"conversion_rate_oracle_contract\":null,\"lockup_time\":0,\"max_mintable_tokens\":40,\"memo\":\"b0b70c2f-5895-42b7-bfbf-0992bd815271\",\"meta_hash\":\"d6c999bbbd5f3c122d2261e2b787acb3e03aac6a1852024b9656c85d7a99fabd\",\"meta_uris\":[\"https://s3.us-east-1.wasabisys.com/ultraio-uniq-staging/d6c999bbbd5f3c122d2261e2b787acb3e03aac6a1852024b9656c85d7a99fabd.zip\"],\"minimum_resell_price\":\"0.00000000 USD\",\"mintable_window_end\":null,\"mintable_window_start\":null,\"recall_window_end\":null,\"recall_window_start\":0,\"resale_shares\":[{\"basis_point\":500,\"receiver\":\"uk1ob2ed3so4\"},{\"basis_point\":500,\"receiver\":\"vl1jt2hi3vd4\"}],\"stat\":null,\"trading_window_end\":null,\"trading_window_start\":null,\"version\":0}}", + "raw_data": "ASRiMGI3MGMyZi01ODk1LTQyYjctYmZiZi0wOTkyYmQ4MTUyNzEBAAAAAAAAAAABkBfIawJzc9QBkBfIawJzc9QAAAEAAAAAAAAAAAhVU0QAAAAAAQJAKB5JiUMD1PQBQNIeron8Qtz0AQAAAAABAAAAAAABKAAAAAEAAAAAAQGQF8hrAnNz1AABAXxodHRwczovL3MzLnVzLWVhc3QtMS53YXNhYmlzeXMuY29tL3VsdHJhaW8tdW5pcS1zdGFnaW5nL2Q2Yzk5OWJiYmQ1ZjNjMTIyZDIyNjFlMmI3ODdhY2IzZTAzYWFjNmExODUyMDI0Yjk2NTZjODVkN2E5OWZhYmQuemlwAdbJmbu9XzwSLSJh4reHrLPgOqxqGFICS5ZWyF16mfq9" + }, + "elapsed": 104, + "transaction_id": "a2a53dce154c2ccdca52a981318775938de02f7efef88926ae1d7fd992988530", + "block_num": 30080032, + "producer_block_id": "01cafc203bf4bf807266fe5ac12c4b9ef72bd6482367a6c95bff70161dbeb462", + "block_time": { + "seconds": 1637144179 + }, + "account_ram_deltas": [ + { + "account": "ultra.nft.ft", + "delta": 378 + } + ], + "action_ordinal": 1, + "filtering_matched": true + } + ], + "db_ops": [ + { + "operation": 2, + "code": "eosio.nft.ft", + "table_name": "next.factory", + "primary_key": "next.factory", + "old_payer": "eosio.nft.ft", + "new_payer": "eosio.nft.ft", + "old_data": "GgAAAAAAAAA=", + "new_data": "GwAAAAAAAAA=" + }, + { + "operation": 1, + "code": "eosio.nft.ft", + "scope": "eosio.nft.ft", + "table_name": "factory.a", + "primary_key": "...........1e", + "new_payer": "ultra.nft.ft", + "new_data": "GgAAAAAAAACQF8hrAnNz1JAXyGsCc3PUAAAAAAAAAAAAAAAAAAAAAAAIVVNEAAAAAAJAKB5JiUMD1PQBQNIeron8Qtz0AQAAAAABAAAAAAABAAAAAAGQF8hrAnNz1AABfGh0dHBzOi8vczMudXMtZWFzdC0xLndhc2FiaXN5cy5jb20vdWx0cmFpby11bmlxLXN0YWdpbmcvZDZjOTk5YmJiZDVmM2MxMjJkMjI2MWUyYjc4N2FjYjNlMDNhYWM2YTE4NTIwMjRiOTY1NmM4NWQ3YTk5ZmFiZC56aXDWyZm7vV88Ei0iYeK3h6yz4DqsahhSAkuWVshdepn6vQEoAAAAAAAAAAAAAAA=" + } + ], + "ram_ops": [ + { + "operation": 10, + "payer": "ultra.nft.ft", + "delta": 378, + "usage": 11664, + "namespace": 9, + "unique_key": "eosio.nft.ft:eosio.nft.ft:factory.a:...........1e", + "action": 1 + } + ], + "creation_tree": [ + { + "creator_action_index": -1 + } + ] + } + ], + "unfiltered_transaction_trace_count": 6, + "filtered_transaction_trace_count": 1, + "unfiltered_executed_input_action_count": 6, + "filtered_executed_input_action_count": 1, + "unfiltered_executed_total_action_count": 6, + "filtered_executed_total_action_count": 1, + "filtering_applied": true, + "filtering_include_filter_expr": "executed \u0026\u0026 action==\"create\" \u0026\u0026 account==\"eosio.nft.ft\" \u0026\u0026 receiver==\"eosio.nft.ft\"" +} \ No newline at end of file diff --git a/testdata/block-43922498.pb.json b/testdata/block-43922498.pb.json new file mode 100644 index 0000000..76a5177 --- /dev/null +++ b/testdata/block-43922498.pb.json @@ -0,0 +1 @@ +{"id":"029e34424c47c0ec60a55177bee0fc2abf8062f920571c591dedacf3616f7e3a","number":43922498,"version":1,"header":{"timestamp":"2023-10-04T15:53:02Z","producer":"produceracc3","previous":"029e34413774fba1d3dad4a89491199190e7281df4755a35f6f0d8d3864ea227","transactionMroot":"HeGpTYtoEeS8gE6GIBUdejDQH8W20mqtE1LQfE6kA18=","actionMroot":"U50AMBhpUibaYXNUba/DtOS85uDiyjtyUvt53e2ZPKs=","scheduleVersion":13},"producerSignature":"SIG_K1_KfbAqEeYXyXCxNXFacbgar5xPPvZYrgGsjT4wscJoavwVKXdVojZv47FmBYftubvKWi88hYevUNBzAeJgQZTSxXh1rcRPo","dposProposedIrreversibleBlocknum":43922481,"dposIrreversibleBlocknum":43922457,"blockrootMerkle":{"nodeCount":43922497,"activeNodes":["Ap40QTd0+6HT2tSolJEZkZDnKB30dVo19vDY04ZOoic=","dGd1TPHiSeXRVtftZvdzYclGQfCMl5Kg2TuRT2E5NJg=","16OncSvcSZ866dWkto1/RsVpywTR2TghSvoKz1WjH2Q=","h6CV9spsFas7IMJ3CV3spwMuA1AfFCCwH3q00XCFsrQ=","+H3lTP4IkA/2mESaPWsBiN2GiBs5KMEA243ookOChds=","etl5OFhkRWQ2OdyAzBg35gcs1/ylH+DVhY4tTsAQD/0=","CgZ+RQ/d4eayb1F7eoHIzAVKEGLJd9xYFhZlAdDCy0g=","Zpn3xFddgQpK/qTdHMmFfzBzuJ0L5bXc9Sh6vSa/MOM=","cUUinUTqlqQXEvUxptXdfQ/SEJn0mIP456a1uFqCNkQ=","z04qz0bhu3bg+tzX2UnJLgDNX0HoYUNS1ekxUwetjo0=","W0B6ZWLzXT23ezLVMug9HB1k/SA92hTbLQBFXiGjaGg=","kyYn2fJYFesdgjQhXfbajahdIOX15G/YfGeAUHJh9/o="]},"producerToLastProduced":[{"name":"produceracc1","lastBlockNumProduced":43922481},{"name":"produceracc2","lastBlockNumProduced":43922493},{"name":"produceracc3","lastBlockNumProduced":43922498}],"producerToLastImpliedIrb":[{"name":"produceracc1","lastBlockNumProduced":43922457},{"name":"produceracc2","lastBlockNumProduced":43922469},{"name":"produceracc3","lastBlockNumProduced":43922481}],"confirmCount":[1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2],"pendingSchedule":{"scheduleLibNum":39401455,"scheduleHash":"gncHvSH9qwCztzTK/U+3XvtoC0s9qBnqlMeapWQTra0=","scheduleV2":{"version":13}},"activatedProtocolFeatures":{"protocolFeatures":["DsfggBd7LAKyeNUIhhFoa0nXOZJaktm/ys1/xrdAU70=","D+GFuIMhUYZYxU8h6R14fymGnFvA3T/HgA95iDwUAw0=","GltEdlcoFG1xuRadzlKjHV+sIUO2GwCLfKkyj92LrEU=","GpmlnYfgbgnsWwKKnLt3SbSlrYgZAENl0C3EN5qLckE=","JlL1+WAGKUEJs90LveY2k/VTJK9FK3me4TeoGpBe7SU=","KZ3LavaSMkuJmznxbVpTCjMGKATkHwncl+nxVrRHZwc=","SpDADVVFTcWwWQVcohNXnG6oVpZ3EqVgF0h4hqTUzA8=","TnvzSNoAqUVImypoF0nrVvXeALkAAU4Tfdrjn0j2nWc=","T8qL2Cu9GB5xTig/g+G0XZXKWvQPuJrTl3tlPESPeMI=","aNyqNMBRfRlmbmszrdZzUdjF9p6ZnKHjeTG8QQopdCg=","i6Uv56OVbFzTplajF0uTHTuyq7RVeL78WfKD7NgWpAU=","rZ49j2UGh3Cf1o9LkLQffYJaNlsCwjpjbO+IrCrADEM=","rqJFf5Ud6QoTLRowexOsEnXI/CSQ0nQ53UVxU9wDr48=","sLrd2WfnkQuQVpYiShE0yoMsUcth2RpqIiZSqCcVxj8=","tUHVRFIr5UqAt+N9TvxkCLdTpxQ7oQ8XDlYCpPq4wbo=","tnweAOC5Pppk0GviuW23tvSufzPvZ1bV8sL+taeCqEM=","y3nUSfgZS/a0A+QP2jGaHi+dKyEI97bLmBZi5jk+1rk=","4PtksQhcxVOJcBWNBaAJwk4nb7lOGgv2pSi0j7xP9SY=","70MRLGVDuI2yKDouB3J4wxWuLIRxmosl8lzIhWX76pk=","8K9W0sWkjWCkpbXJA+37fbOnNqlO1YnQt5ffM/+dPh0=","/Si5iGxEafNAYqQZIy9NW4AH15C4HMxtZ1/slB59gLs="]},"rlimitOps":[{"operation":"OPERATION_UPDATE","state":{"averageBlockNetUsage":{"lastOrdinal":43922497,"valueEx":"100866891","consumed":"133"},"averageBlockCpuUsage":{"lastOrdinal":43922497,"valueEx":"36231670","consumed":"47"},"pendingNetUsage":"1112","pendingCpuUsage":"421","totalNetWeight":"10100002","totalCpuWeight":"10100002","totalRamBytes":"4296727552","virtualNetLimit":"1048576","virtualCpuLimit":"400000"}},{"operation":"OPERATION_UPDATE","state":{"averageBlockNetUsage":{"lastOrdinal":43922498,"valueEx":"109293000","consumed":"1213"},"averageBlockCpuUsage":{"lastOrdinal":43922498,"valueEx":"39438073","consumed":"457"},"totalNetWeight":"10100002","totalCpuWeight":"10100002","totalRamBytes":"4296727552","virtualNetLimit":"1048576","virtualCpuLimit":"400000"}}],"filteredTransactions":[{"id":"e4a1c02a1997df6dae077c2efa49056346a96ae55de509996481525845af6efe","status":"TRANSACTIONSTATUS_EXECUTED","cpuUsageMicroSeconds":411,"netUsageWords":135,"packedTransaction":{"signatures":["SIG_K1_JuZCxE71RfVkXXEL4yL5v91qnfBbt4Kmce2gY48KawXv8y9nYPYZbmQVMnmK95gSTuyi5PdEN2UA8MVkX6qDwEZpQYqjW5"],"packedTransaction":"l4odZTw0AYQg0AAAAAABoCIylwLqMFUAAAAqm9uwrgGgIjKXAnNz1AAAACqb27CuMQAAAKAbmd3RAVqKHWUAAAAASNHqAAAAAAAIRFVPUwAAANgW/ulhEgAACFVTRAAAAAAA"}}],"unfilteredTransactionCount":1,"filteredTransactionCount":1,"filteredTransactionTraces":[{"id":"e4a1c02a1997df6dae077c2efa49056346a96ae55de509996481525845af6efe","blockNum":"43922498","index":"1","blockTime":"2023-10-04T15:53:02Z","producerBlockId":"029e34424c47c0ec60a55177bee0fc2abf8062f920571c591dedacf3616f7e3a","receipt":{"status":"TRANSACTIONSTATUS_EXECUTED","cpuUsageMicroSeconds":411,"netUsageWords":135},"elapsed":"357","netUsage":"1080","actionTraces":[{"receiver":"eosio.oracle","receipt":{"receiver":"eosio.oracle","digest":"6357d9e3d282877f681884925077f0fbe00fc2c7954e987c49baf4e274e47ce8","globalSequence":"45240773","authSequence":[{"accountName":"ultra.oracle","sequence":"1266926"}],"recvSequence":"1266921","codeSequence":"2","abiSequence":"2"},"action":{"account":"eosio.oracle","name":"pushrate","authorization":[{"actor":"ultra.oracle","permission":"pushrate"}],"jsonData":"{\"exchange\":\"ubitmax\",\"rates\":[{\"price\":\"0.15389000 DUOS\",\"timestamp\":1696434778}],\"volume\":\"202117.46871000 USD\"}","rawData":"AAAAoBuZ3dEBWoodZQAAAABI0eoAAAAAAAhEVU9TAAAA2Bb+6WESAAAIVVNEAAAAAA=="},"elapsed":"269","transactionId":"e4a1c02a1997df6dae077c2efa49056346a96ae55de509996481525845af6efe","blockNum":"43922498","producerBlockId":"029e34424c47c0ec60a55177bee0fc2abf8062f920571c591dedacf3616f7e3a","blockTime":"2023-10-04T15:53:02Z","actionOrdinal":1,"filteringMatched":true}],"dbOps":[{"operation":"OPERATION_UPDATE","code":"eosio.oracle","scope":"eosio.oracle","tableName":"feeddata","primaryKey":"ubitmax","oldPayer":"ultra.oracle","newPayer":"ultra.oracle","oldData":"AAAAoBuZ3dF4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGFbrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4B+sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIbesAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWJEl02ASAAAA","newData":"AAAAoBuZ3dF4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGFbrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4B+sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASNHqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIbesAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2Bb+6WESAAAA"},{"operation":"OPERATION_UPDATE","code":"eosio.oracle","scope":".1docnmjch2p3","tableName":"finalaverage","primaryKey":".........31p","oldPayer":"ultra.oracle","newPayer":"ultra.oracle","oldData":"SIodZQAAAACoweoAAAAAAAhEVU9TAAAAAFDDAAAAAAAAAQE=","newData":"WoodZQAAAABI0eoAAAAAAAhEVU9TAAAAAFDDAAAAAAAAAQE="},{"operation":"OPERATION_UPDATE","code":"eosio.oracle","scope":"eosio.oracle","tableName":"oraclestate","primaryKey":"oraclestate","oldPayer":"ultra.oracle","newPayer":"ultra.oracle","oldData":"CERVT1MAAAAIVVNEAAAAAEiKHWUAAAAAAXgEAAAABAAAAA==","newData":"CERVT1MAAAAIVVNEAAAAAFqKHWUAAAAAAXgEAAAABAAAAA=="},{"operation":"OPERATION_UPDATE","code":"eosio.oracle","scope":"eosio.oracle","tableName":"lastknwnrate","primaryKey":"lastknwnrate","oldPayer":"ultra.oracle","newPayer":"ultra.oracle","oldData":"AAAAYDqKNNRIih1lAAAAAKjB6gAAAAAACERVT1MAAAA=","newData":"AAAAoBuZ3dFaih1lAAAAAEjR6gAAAAAACERVT1MAAAA="}],"rlimitOps":[{"operation":"OPERATION_UPDATE","accountUsage":{"owner":"ultra.oracle","netUsage":{"lastOrdinal":1499499964,"valueEx":"57907459","consumed":"1138"},"cpuUsage":{"lastOrdinal":1499499964,"valueEx":"22048477","consumed":"434"},"ramUsage":"27794"}}],"creationTree":[{"creatorActionIndex":-1}]}],"unfilteredTransactionTraceCount":2,"filteredTransactionTraceCount":1,"unfilteredExecutedInputActionCount":2,"filteredExecutedInputActionCount":1,"unfilteredExecutedTotalActionCount":2,"filteredExecutedTotalActionCount":1,"validBlockSigningAuthorityV2":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS8dajNwmCPVfr2cNzyWZDwucn5CCPV7nRCYoBB2XAcgCEBWRctX","weight":1}]}},"activeScheduleV2":{"version":13,"producers":[{"accountName":"produceracc1","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS8dajNwmCPVfr2cNzyWZDwucn5CCPV7nRCYoBB2XAcgCEBWRctX","weight":1}]}}},{"accountName":"produceracc2","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS8dajNwmCPVfr2cNzyWZDwucn5CCPV7nRCYoBB2XAcgCEBWRctX","weight":1}]}}},{"accountName":"produceracc3","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS8dajNwmCPVfr2cNzyWZDwucn5CCPV7nRCYoBB2XAcgCEBWRctX","weight":1}]}}}]},"filteringApplied":true,"filteringIncludeFilterExpr":"(account==\"eosio.oracle\" \u0026\u0026 receiver==\"eosio.oracle\") || (action==\"setabi\" \u0026\u0026 account==\"eosio\" \u0026\u0026 data.account==\"eosio.oracle\")"} \ No newline at end of file diff --git a/testdata/block-48096934.pb.json b/testdata/block-48096934.pb.json new file mode 100644 index 0000000..1ace68a --- /dev/null +++ b/testdata/block-48096934.pb.json @@ -0,0 +1,471 @@ +{ + "id": "02dde6a678883fa7a1ea8742f49d246d54a6a1de9452de6288fa92b433d5abfc", + "number": 48096934, + "version": 1, + "header": { + "timestamp": "2022-03-01T16:48:59.500Z", + "producer": "ivote4eosusa", + "previous": "02dde6a515e06d8309aa0d9fedb1cbb64a157f559723213ff03eb4014cff8cbd", + "transactionMroot": "GPXY9egAZiq9r+3bfHEn0jBn8IOOhNObr9wUWMkcavg=", + "actionMroot": "cpLx94kE0HSV+ZwGSUT8ZnewMOrvQAGMex6lFku9kio=", + "scheduleVersion": 23 + }, + "producerSignature": "SIG_K1_K9jWTWm7YeBtXZkFTszZqnsFeXxhKkzPN2YGyMc31ooXV7hc4WxLtEzFtn9iznELqex4qpeGwz1qA8LPaxAFpESNHdxT4h", + "dposProposedIrreversibleBlocknum": 48096886, + "dposIrreversibleBlocknum": 48096838, + "blockrootMerkle": { + "nodeCount": 48096933, + "activeNodes": [ + "At3mpRXgbYMJqg2f7bHLtkoVf1WXIyE/8D60AUz/jL0=", + "BOcQgFsfEeSKaeH9BkguFKuB22chPYrg67Pvp6Vgypk=", + "4+alDlfH1GYANR18NuzUQlDnfI+6goG5DH0eTU5xsYg=", + "3+3q8eIQWkRWRApAUSLFZDfeEAJ2Ke0+0NjJatWa/b4=", + "nXOgfd2XsJ1Wqjs4q/jYPw9XFAfx0Hrat6CYzfhjyQg=", + "Rz/luEyCFZeRGt8jIMReLq6i0Rq9l/c1MDErK7i51XI=", + "qTWFwR+p9/TkeYt8KoCLNu1Jv94d/6ipOBFVp4yDFZA=", + "v5rTH0uYOvtyTwJUyEfam9XSo+8eEVBYhzbT8AHvlAg=", + "e3nHGvHG6E4Q7Fv3N49457xqqPNs02rGOUzvt1Chfec=", + "mL/7WvV7cUim2VCMPXTUgDxu3qbwE5wfRocunccKUVI=", + "OGlksAVtDt6YHRW0ZjY2kTTLJm6Pvn3L074WhwEL86I=", + "26MQqrPolqpx0fCT0+3PAvI08etghktIGDIRP4IGF00=", + "jJyWo+En9HO6mRnTN+hnXxcPAp6Z5pwcBus4TULDTVw=", + "nS1/W7dtTpB3lxndStiQsUGxUPV0e0vH7vepD/OYnZ0=", + "xo8U7wXIJsMirafYsrkiRLxlodnsyrVJ0JKrfk+bWBg=", + "nzFxSRYd5vBXIeFSpUbwbrXUjdfWtkY0nPEySxAO9Ko=", + "Y7g2i5GKbL2CEmgdnNjHEUl6P635gzvAO4C6O0ZUuNs=" + ] + }, + "producerToLastProduced": [ + { + "name": "cryptolions1", + "lastBlockNumProduced": 48096886 + }, + { + "name": "eosnationftw", + "lastBlockNumProduced": 48096922 + }, + { + "name": "eosriobrazil", + "lastBlockNumProduced": 48096874 + }, + { + "name": "eosubisoft1", + "lastBlockNumProduced": 48096898 + }, + { + "name": "ivote4eosusa", + "lastBlockNumProduced": 48096934 + }, + { + "name": "uoseouldotio", + "lastBlockNumProduced": 48096862 + }, + { + "name": "uosswedenorg", + "lastBlockNumProduced": 48096910 + } + ], + "producerToLastImpliedIrb": [ + { + "name": "cryptolions1", + "lastBlockNumProduced": 48096838 + }, + { + "name": "eosnationftw", + "lastBlockNumProduced": 48096874 + }, + { + "name": "eosriobrazil", + "lastBlockNumProduced": 48096826 + }, + { + "name": "eosubisoft1", + "lastBlockNumProduced": 48096850 + }, + { + "name": "ivote4eosusa", + "lastBlockNumProduced": 48096886 + }, + { + "name": "uoseouldotio", + "lastBlockNumProduced": 48096814 + }, + { + "name": "uosswedenorg", + "lastBlockNumProduced": 48096862 + } + ], + "confirmCount": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4 + ], + "pendingSchedule": { + "scheduleLibNum": 31206479, + "scheduleHash": "0kV64BEe9ZeDfHNPg++pnKzQpRty/TWNE0fduNUrC6o=", + "scheduleV2": { + "version": 23 + } + }, + "activatedProtocolFeatures": { + "protocolFeatures": [ + "DsfggBd7LAKyeNUIhhFoa0nXOZJaktm/ys1/xrdAU70=", + "GpmlnYfgbgnsWwKKnLt3SbSlrYgZAENl0C3EN5qLckE=", + "JlL1+WAGKUEJs90LveY2k/VTJK9FK3me4TeoGpBe7SU=", + "KZ3LavaSMkuJmznxbVpTCjMGKATkHwncl+nxVrRHZwc=", + "SpDADVVFTcWwWQVcohNXnG6oVpZ3EqVgF0h4hqTUzA8=", + "TnvzSNoAqUVImypoF0nrVvXeALkAAU4Tfdrjn0j2nWc=", + "T8qL2Cu9GB5xTig/g+G0XZXKWvQPuJrTl3tlPESPeMI=", + "aNyqNMBRfRlmbmszrdZzUdjF9p6ZnKHjeTG8QQopdCg=", + "i6Uv56OVbFzTplajF0uTHTuyq7RVeL78WfKD7NgWpAU=", + "rZ49j2UGh3Cf1o9LkLQffYJaNlsCwjpjbO+IrCrADEM=", + "4PtksQhcxVOJcBWNBaAJwk4nb7lOGgv2pSi0j7xP9SY=", + "70MRLGVDuI2yKDouB3J4wxWuLIRxmosl8lzIhWX76pk=", + "8K9W0sWkjWCkpbXJA+37fbOnNqlO1YnQt5ffM/+dPh0=" + ] + }, + "rlimitOps": [ + { + "operation": "OPERATION_UPDATE", + "state": { + "averageBlockNetUsage": { + "lastOrdinal": 48096933, + "valueEx": "430600123", + "consumed": "986" + }, + "averageBlockCpuUsage": { + "lastOrdinal": 48096933, + "valueEx": "159482278", + "consumed": "365" + }, + "pendingNetUsage": "576", + "pendingCpuUsage": "213", + "totalNetWeight": "101000000", + "totalCpuWeight": "101000000", + "totalRamBytes": "217146944", + "virtualNetLimit": "1048576", + "virtualCpuLimit": "400000" + } + }, + { + "operation": "OPERATION_UPDATE", + "state": { + "averageBlockNetUsage": { + "lastOrdinal": 48096934, + "valueEx": "431811788", + "consumed": "1004" + }, + "averageBlockCpuUsage": { + "lastOrdinal": 48096934, + "valueEx": "159928259", + "consumed": "372" + }, + "totalNetWeight": "101000000", + "totalCpuWeight": "101000000", + "totalRamBytes": "217146944", + "virtualNetLimit": "1048576", + "virtualCpuLimit": "400000" + } + } + ], + "filteredTransactions": [ + { + "id": "5edbdedeab29c65e77839d3212ce1f670e0ab8b703ed421c614ad046ee6fbe16", + "index": "1", + "status": "TRANSACTIONSTATUS_EXECUTED", + "cpuUsageMicroSeconds": 144, + "netUsageWords": 48, + "packedTransaction": { + "signatures": [ + "SIG_K1_JyHqhZuCBBVXmyXdvEppmAg99mLvGZ7CyPwDXXq1UKShWei9LfaMCCx5WgN2KCbYSgPL8iHKAAJh8sNYaK8tCw94hh6max" + ], + "packedTransaction": "6k4eYpPmmcDJ2QAAAAABkBfIawLqMFUAAAAAqGzURQGQF8hrAnNz1AAAACBNBZE5nwIBJGI2ZGEyNjk1LThmMjUtNDliYi04Y2YxLTAyODU0YzE2ODE2ZgEAAAAAAAAAAAGQF8hrAnNz1AGQF8hrAnNz1AAAAQDh9QUAAAAACFVPUwAAAAABAZAXyGsCc3PU6AMAAAAAAQAAAAAAAQIAAAABAAAAAAEBkBfIawJzc9QAAQF8aHR0cHM6Ly9zMy51cy1lYXN0LTEud2FzYWJpc3lzLmNvbS91bHRyYWlvLXVuaXEtc3RhZ2luZy80YzNlZTUxMGMzYzBkOThjZjI1MjExYzQ5ODE2YTJhN2IzODgzZmZkYTk1NzIzODdmZTQ0MzBjZjNiNzMxYjVlLnppcAFMPuUQw8DZjPJSEcSYFqKns4g//alXI4f+RDDPO3MbXgA=" + } + } + ], + "unfilteredTransactionCount": 2, + "filteredTransactionCount": 1, + "filteredTransactionTraces": [ + { + "id": "5edbdedeab29c65e77839d3212ce1f670e0ab8b703ed421c614ad046ee6fbe16", + "blockNum": "48096934", + "index": "2", + "blockTime": "2022-03-01T16:48:59.500Z", + "producerBlockId": "02dde6a678883fa7a1ea8742f49d246d54a6a1de9452de6288fa92b433d5abfc", + "receipt": { + "status": "TRANSACTIONSTATUS_EXECUTED", + "cpuUsageMicroSeconds": 144, + "netUsageWords": 48 + }, + "elapsed": "458", + "netUsage": "384", + "actionTraces": [ + { + "receiver": "eosio.nft.ft", + "receipt": { + "receiver": "eosio.nft.ft", + "digest": "4b0e3b306fb436dbf199d68df5e917206344c9d19c7fda8eb78b176bab179e87", + "globalSequence": "125827656", + "authSequence": [ + { + "accountName": "ultra.nft.ft", + "sequence": "187" + } + ], + "recvSequence": "214", + "codeSequence": "5", + "abiSequence": "5" + }, + "action": { + "account": "eosio.nft.ft", + "name": "create", + "authorization": [ + { + "actor": "ultra.nft.ft", + "permission": "backend" + } + ], + "jsonData": "{\"create\":{\"asset_creator\":\"ultra.nft.ft\",\"asset_manager\":\"ultra.nft.ft\",\"chosen_rate\":null,\"conditionless_receivers\":[\"ultra.nft.ft\"],\"conversion_rate_oracle_contract\":null,\"lockup_time\":0,\"max_mintable_tokens\":2,\"memo\":\"b6da2695-8f25-49bb-8cf1-02854c16816f\",\"meta_hash\":\"4c3ee510c3c0d98cf25211c49816a2a7b3883ffda9572387fe4430cf3b731b5e\",\"meta_uris\":[\"https://s3.us-east-1.wasabisys.com/ultraio-uniq-staging/4c3ee510c3c0d98cf25211c49816a2a7b3883ffda9572387fe4430cf3b731b5e.zip\"],\"minimum_resell_price\":\"1.00000000 UOS\",\"mintable_window_end\":null,\"mintable_window_start\":null,\"recall_window_end\":null,\"recall_window_start\":0,\"resale_shares\":[{\"basis_point\":1000,\"receiver\":\"ultra.nft.ft\"}],\"stat\":null,\"trading_window_end\":null,\"trading_window_start\":null,\"version\":0}}", + "rawData": "ASRiNmRhMjY5NS04ZjI1LTQ5YmItOGNmMS0wMjg1NGMxNjgxNmYBAAAAAAAAAAABkBfIawJzc9QBkBfIawJzc9QAAAEA4fUFAAAAAAhVT1MAAAAAAQGQF8hrAnNz1OgDAAAAAAEAAAAAAAECAAAAAQAAAAABAZAXyGsCc3PUAAEBfGh0dHBzOi8vczMudXMtZWFzdC0xLndhc2FiaXN5cy5jb20vdWx0cmFpby11bmlxLXN0YWdpbmcvNGMzZWU1MTBjM2MwZDk4Y2YyNTIxMWM0OTgxNmEyYTdiMzg4M2ZmZGE5NTcyMzg3ZmU0NDMwY2YzYjczMWI1ZS56aXABTD7lEMPA2YzyUhHEmBaip7OIP/2pVyOH/kQwzztzG14=" + }, + "elapsed": "422", + "transactionId": "5edbdedeab29c65e77839d3212ce1f670e0ab8b703ed421c614ad046ee6fbe16", + "blockNum": "48096934", + "producerBlockId": "02dde6a678883fa7a1ea8742f49d246d54a6a1de9452de6288fa92b433d5abfc", + "blockTime": "2022-03-01T16:48:59.500Z", + "accountRamDeltas": [ + { + "account": "ultra.nft.ft", + "delta": "368" + } + ], + "actionOrdinal": 1, + "filteringMatched": true + } + ], + "dbOps": [ + { + "operation": "OPERATION_UPDATE", + "code": "eosio.nft.ft", + "tableName": "next.factory", + "primaryKey": "next.factory", + "oldPayer": "eosio.nft.ft", + "newPayer": "eosio.nft.ft", + "oldData": "LAAAAAAAAAA=", + "newData": "LQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "eosio.nft.ft", + "scope": "eosio.nft.ft", + "tableName": "factory.a", + "primaryKey": "...........2g", + "newPayer": "ultra.nft.ft", + "newData": "LAAAAAAAAACQF8hrAnNz1JAXyGsCc3PUAAAAAAAAAAAAAOH1BQAAAAAIVU9TAAAAAAGQF8hrAnNz1OgDAAAAAAEAAAAAAAEAAAAAAZAXyGsCc3PUAAF8aHR0cHM6Ly9zMy51cy1lYXN0LTEud2FzYWJpc3lzLmNvbS91bHRyYWlvLXVuaXEtc3RhZ2luZy80YzNlZTUxMGMzYzBkOThjZjI1MjExYzQ5ODE2YTJhN2IzODgzZmZkYTk1NzIzODdmZTQ0MzBjZjNiNzMxYjVlLnppcEw+5RDDwNmM8lIRxJgWoqeziD/9qVcjh/5EMM87cxteAQIAAAAAAAAAAAAAAA==" + } + ], + "ramOps": [ + { + "operation": "OPERATION_PRIMARY_INDEX_ADD", + "payer": "ultra.nft.ft", + "delta": "368", + "usage": "19360", + "namespace": "NAMESPACE_TABLE_ROW", + "uniqueKey": "eosio.nft.ft:eosio.nft.ft:factory.a:...........2g", + "action": "ACTION_ADD" + } + ], + "rlimitOps": [ + { + "operation": "OPERATION_UPDATE", + "accountUsage": { + "owner": "ultra.nft.ft", + "netUsage": { + "lastOrdinal": 1398937079, + "valueEx": "4035", + "consumed": "385" + }, + "cpuUsage": { + "lastOrdinal": 1398937079, + "valueEx": "1526", + "consumed": "145" + }, + "ramUsage": "19360" + } + } + ], + "creationTree": [ + { + "creatorActionIndex": -1 + } + ] + } + ], + "unfilteredTransactionTraceCount": 3, + "filteredTransactionTraceCount": 1, + "unfilteredExecutedInputActionCount": 3, + "filteredExecutedInputActionCount": 1, + "unfilteredExecutedTotalActionCount": 3, + "filteredExecutedTotalActionCount": 1, + "validBlockSigningAuthorityV2": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5gKJLRWxMgLWDsvKu7YaTweoQpqin5SSzJ4mDuaXnQ6tZb1F31", + "weight": 1 + } + ] + } + }, + "activeScheduleV2": { + "version": 23, + "producers": [ + { + "accountName": "eosriobrazil", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS7nq54FcLXtDtY8GHqknYk5NqZPZNBVY3wCJQeRrzVzVjyXsEM6", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "cryptolions1", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS8LioNSB3ugUG7LweRBB2BR8AMJUEFGicXMs9msHByd9vMmxEKe", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "eosubisoft1", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5FgTg6xtmMaonVo3uo4S8oLTWTEUnSagAMQCvUkczu9e5hXaWp", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "uosswedenorg", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS6j7tqvStvcykc1ysDuNY1J8PqyARpGqWrxbVjiuNBQL8uL7r4A", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "eosnationftw", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5uMMtMvBp4pPEBiUsfdBR9LVnJnzUonGFvQtwnh1eoXzZZKN8U", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "ivote4eosusa", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5gKJLRWxMgLWDsvKu7YaTweoQpqin5SSzJ4mDuaXnQ6tZb1F31", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "uoseouldotio", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS7Tauu774Uz1bisTGUC37NsS6mxYoNaW3QKYF3oD1bUUJdCGxKf", + "weight": 1 + } + ] + } + } + } + ] + }, + "filteringApplied": true, + "filteringIncludeFilterExpr": "executed \u0026\u0026 (action==\"create\" || action==\"issue\") \u0026\u0026 account==\"eosio.nft.ft\" \u0026\u0026 receiver==\"eosio.nft.ft\"" +} \ No newline at end of file diff --git a/testdata/block-48098158.pb.json b/testdata/block-48098158.pb.json new file mode 100644 index 0000000..4f56957 --- /dev/null +++ b/testdata/block-48098158.pb.json @@ -0,0 +1,483 @@ +{ + "id": "02ddeb6eb1e53f5d1b4f12d7844c7eaf1a57f2a03321dff94f4ebad75f85a06e", + "number": 48098158, + "version": 1, + "header": { + "timestamp": "2022-03-01T16:59:11.500Z", + "producer": "eosubisoft1", + "previous": "02ddeb6da45be71b83d3c14e41a711a815bcac7fcc73bddf2d0bbcf79f26fe0e", + "transactionMroot": "Jq0tHSTCjfb+Hhce+QtUbpyNHXRih+lQvUKxvV7JJLU=", + "actionMroot": "Vnjxkp55QYvBk2KCtZQIB8sGu+qs8+6Uxto4S5VXZCw=", + "scheduleVersion": 23 + }, + "producerSignature": "SIG_K1_Kep666JceXi4bzQ3FcKnY78XLKzsve7P71V1pLZfbGw9LhXJ8vuYs8QwAiNAeJuQgKYpCkUirb1YJr9D1YxchmVJLBVtsz", + "dposProposedIrreversibleBlocknum": 48098110, + "dposIrreversibleBlocknum": 48098062, + "blockrootMerkle": { + "nodeCount": 48098157, + "activeNodes": [ + "At3rbaRb5xuD08FOQacRqBW8rH/Mc73fLQu8958m/g4=", + "59k5khWMLwpeyd3Gum3Miifn1w/ddUzGgntBJ2W7G0M=", + "YLvS2P/RrRUIi/U5ysnbu5cIV+Mu1yN1dNEhdr2YYbc=", + "YcXoNu2uq6irO4mhfD2STkH6KEndqfFtFUSZ5fdJdOY=", + "hfQqUSzm/Fe81c6e/uIa4se1tuap8VWrRCBhpc5AoiA=", + "vT3tiC3rF/FGqsfnq07STOQtrvSc4xl+0eq3HESwkow=", + "8HxrB+kdmmmCufcwRmvhPLEl5v4YcNqLmJ+iHdPvfIA=", + "KsNOi/KJUDtLRRYtZMUynrqCvCBTZpvn5Rrqg65+iiQ=", + "qTWFwR+p9/TkeYt8KoCLNu1Jv94d/6ipOBFVp4yDFZA=", + "v5rTH0uYOvtyTwJUyEfam9XSo+8eEVBYhzbT8AHvlAg=", + "e3nHGvHG6E4Q7Fv3N49457xqqPNs02rGOUzvt1Chfec=", + "mL/7WvV7cUim2VCMPXTUgDxu3qbwE5wfRocunccKUVI=", + "OGlksAVtDt6YHRW0ZjY2kTTLJm6Pvn3L074WhwEL86I=", + "26MQqrPolqpx0fCT0+3PAvI08etghktIGDIRP4IGF00=", + "jJyWo+En9HO6mRnTN+hnXxcPAp6Z5pwcBus4TULDTVw=", + "nS1/W7dtTpB3lxndStiQsUGxUPV0e0vH7vepD/OYnZ0=", + "xo8U7wXIJsMirafYsrkiRLxlodnsyrVJ0JKrfk+bWBg=", + "nzFxSRYd5vBXIeFSpUbwbrXUjdfWtkY0nPEySxAO9Ko=", + "E8jglZ6vktxxo5rNK0H33pFXLY1Tt6GADJoGi8Sgc90=" + ] + }, + "producerToLastProduced": [ + { + "name": "cryptolions1", + "lastBlockNumProduced": 48098146 + }, + { + "name": "eosnationftw", + "lastBlockNumProduced": 48098098 + }, + { + "name": "eosriobrazil", + "lastBlockNumProduced": 48098134 + }, + { + "name": "eosubisoft1", + "lastBlockNumProduced": 48098158 + }, + { + "name": "ivote4eosusa", + "lastBlockNumProduced": 48098110 + }, + { + "name": "uoseouldotio", + "lastBlockNumProduced": 48098122 + }, + { + "name": "uosswedenorg", + "lastBlockNumProduced": 48098086 + } + ], + "producerToLastImpliedIrb": [ + { + "name": "cryptolions1", + "lastBlockNumProduced": 48098098 + }, + { + "name": "eosnationftw", + "lastBlockNumProduced": 48098050 + }, + { + "name": "eosriobrazil", + "lastBlockNumProduced": 48098086 + }, + { + "name": "eosubisoft1", + "lastBlockNumProduced": 48098110 + }, + { + "name": "ivote4eosusa", + "lastBlockNumProduced": 48098062 + }, + { + "name": "uoseouldotio", + "lastBlockNumProduced": 48098074 + }, + { + "name": "uosswedenorg", + "lastBlockNumProduced": 48098038 + } + ], + "confirmCount": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4 + ], + "pendingSchedule": { + "scheduleLibNum": 31206479, + "scheduleHash": "0kV64BEe9ZeDfHNPg++pnKzQpRty/TWNE0fduNUrC6o=", + "scheduleV2": { + "version": 23 + } + }, + "activatedProtocolFeatures": { + "protocolFeatures": [ + "DsfggBd7LAKyeNUIhhFoa0nXOZJaktm/ys1/xrdAU70=", + "GpmlnYfgbgnsWwKKnLt3SbSlrYgZAENl0C3EN5qLckE=", + "JlL1+WAGKUEJs90LveY2k/VTJK9FK3me4TeoGpBe7SU=", + "KZ3LavaSMkuJmznxbVpTCjMGKATkHwncl+nxVrRHZwc=", + "SpDADVVFTcWwWQVcohNXnG6oVpZ3EqVgF0h4hqTUzA8=", + "TnvzSNoAqUVImypoF0nrVvXeALkAAU4Tfdrjn0j2nWc=", + "T8qL2Cu9GB5xTig/g+G0XZXKWvQPuJrTl3tlPESPeMI=", + "aNyqNMBRfRlmbmszrdZzUdjF9p6ZnKHjeTG8QQopdCg=", + "i6Uv56OVbFzTplajF0uTHTuyq7RVeL78WfKD7NgWpAU=", + "rZ49j2UGh3Cf1o9LkLQffYJaNlsCwjpjbO+IrCrADEM=", + "4PtksQhcxVOJcBWNBaAJwk4nb7lOGgv2pSi0j7xP9SY=", + "70MRLGVDuI2yKDouB3J4wxWuLIRxmosl8lzIhWX76pk=", + "8K9W0sWkjWCkpbXJA+37fbOnNqlO1YnQt5ffM/+dPh0=" + ] + }, + "rlimitOps": [ + { + "operation": "OPERATION_UPDATE", + "state": { + "averageBlockNetUsage": { + "lastOrdinal": 48098157, + "valueEx": "425900146", + "consumed": "1188" + }, + "averageBlockCpuUsage": { + "lastOrdinal": 48098157, + "valueEx": "157621157", + "consumed": "441" + }, + "pendingNetUsage": "360", + "pendingCpuUsage": "133", + "totalNetWeight": "101000000", + "totalCpuWeight": "101000000", + "totalRamBytes": "217146944", + "virtualNetLimit": "1048576", + "virtualCpuLimit": "400000" + } + }, + { + "operation": "OPERATION_UPDATE", + "state": { + "averageBlockNetUsage": { + "lastOrdinal": 48098158, + "valueEx": "425350978", + "consumed": "783" + }, + "averageBlockCpuUsage": { + "lastOrdinal": 48098158, + "valueEx": "157415981", + "consumed": "290" + }, + "totalNetWeight": "101000000", + "totalCpuWeight": "101000000", + "totalRamBytes": "217146944", + "virtualNetLimit": "1048576", + "virtualCpuLimit": "400000" + } + } + ], + "filteredTransactions": [ + { + "id": "7ed9e6583579ad4816fbc39cdee214b460103612042c23f8618e78bc3115c810", + "status": "TRANSACTIONSTATUS_EXECUTED", + "cpuUsageMicroSeconds": 132, + "netUsageWords": 44, + "packedTransaction": { + "signatures": [ + "SIG_K1_KAmjQLir1mccZoiQhENtvmyMvLhuuZmSnUnPKQQAXbiRqHe7TNhRTWemNG5cUScQp2XT31MVnmNChgw2hfdPZPnBktcWFS" + ], + "packedTransaction": "TlEeYlrrVlKSkAAAAAABkBfIawLqMFUAAAAAAKUxdgGQF8hrAnNz1AAAACBNBZE5NgFAYhy6inSCdwEBLAAAAAAAAAABAAAABG51bGwBGDYyMWU1MGQ1Yzc5MzgxMzZlNWRkNjYzYgA=" + } + } + ], + "unfilteredTransactionCount": 1, + "filteredTransactionCount": 1, + "filteredTransactionTraces": [ + { + "id": "7ed9e6583579ad4816fbc39cdee214b460103612042c23f8618e78bc3115c810", + "blockNum": "48098158", + "index": "1", + "blockTime": "2022-03-01T16:59:11.500Z", + "producerBlockId": "02ddeb6eb1e53f5d1b4f12d7844c7eaf1a57f2a03321dff94f4ebad75f85a06e", + "receipt": { + "status": "TRANSACTIONSTATUS_EXECUTED", + "cpuUsageMicroSeconds": 132, + "netUsageWords": 44 + }, + "elapsed": "186", + "netUsage": "352", + "actionTraces": [ + { + "receiver": "eosio.nft.ft", + "receipt": { + "receiver": "eosio.nft.ft", + "digest": "fb0f0f172f9a4d3df7e3000a9f1ae4ce4329fc765fbc6915c8a1cc2261f1e727", + "globalSequence": "125831320", + "authSequence": [ + { + "accountName": "ultra.nft.ft", + "sequence": "188" + } + ], + "recvSequence": "215", + "codeSequence": "5", + "abiSequence": "5" + }, + "action": { + "account": "eosio.nft.ft", + "name": "issue", + "authorization": [ + { + "actor": "ultra.nft.ft", + "permission": "backend" + } + ], + "jsonData": "{\"issue\":{\"memo\":\"621e50d5c7938136e5dd663b\",\"to\":\"iy1bd2pu3ll4\",\"token_configs\":[{\"amount\":1,\"custom_data\":\"null\",\"token_factory_id\":44}]}}", + "rawData": "AUBiHLqKdIJ3AQEsAAAAAAAAAAEAAAAEbnVsbAEYNjIxZTUwZDVjNzkzODEzNmU1ZGQ2NjNi" + }, + "elapsed": "135", + "transactionId": "7ed9e6583579ad4816fbc39cdee214b460103612042c23f8618e78bc3115c810", + "blockNum": "48098158", + "producerBlockId": "02ddeb6eb1e53f5d1b4f12d7844c7eaf1a57f2a03321dff94f4ebad75f85a06e", + "blockTime": "2022-03-01T16:59:11.500Z", + "accountRamDeltas": [ + { + "account": "eosio.nft.ft", + "delta": "136" + } + ], + "actionOrdinal": 1, + "filteringMatched": true + } + ], + "dbOps": [ + { + "operation": "OPERATION_UPDATE", + "code": "eosio.nft.ft", + "tableName": "next.token", + "primaryKey": "next.token", + "oldPayer": "eosio.nft.ft", + "newPayer": "eosio.nft.ft", + "oldData": "ewAAAAAAAAA=", + "newData": "fAAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "eosio.nft.ft", + "scope": "iy1bd2pu3ll4", + "tableName": "token.a", + "primaryKey": "...........bf", + "newPayer": "eosio.nft.ft", + "newData": "ewAAAAAAAAAsAAAAAAAAAN9QHmIBAAAA" + }, + { + "operation": "OPERATION_UPDATE", + "code": "eosio.nft.ft", + "scope": "eosio.nft.ft", + "tableName": "factory.a", + "primaryKey": "...........2g", + "oldPayer": "ultra.nft.ft", + "newPayer": "ultra.nft.ft", + "oldData": "LAAAAAAAAACQF8hrAnNz1JAXyGsCc3PUAAAAAAAAAAAAAOH1BQAAAAAIVU9TAAAAAAGQF8hrAnNz1OgDAAAAAAEAAAAAAAEAAAAAAZAXyGsCc3PUAAF8aHR0cHM6Ly9zMy51cy1lYXN0LTEud2FzYWJpc3lzLmNvbS91bHRyYWlvLXVuaXEtc3RhZ2luZy80YzNlZTUxMGMzYzBkOThjZjI1MjExYzQ5ODE2YTJhN2IzODgzZmZkYTk1NzIzODdmZTQ0MzBjZjNiNzMxYjVlLnppcEw+5RDDwNmM8lIRxJgWoqeziD/9qVcjh/5EMM87cxteAQIAAAAAAAAAAAAAAA==", + "newData": "LAAAAAAAAACQF8hrAnNz1JAXyGsCc3PUAAAAAAAAAAAAAOH1BQAAAAAIVU9TAAAAAAGQF8hrAnNz1OgDAAAAAAEAAAAAAAEAAAAAAZAXyGsCc3PUAAF8aHR0cHM6Ly9zMy51cy1lYXN0LTEud2FzYWJpc3lzLmNvbS91bHRyYWlvLXVuaXEtc3RhZ2luZy80YzNlZTUxMGMzYzBkOThjZjI1MjExYzQ5ODE2YTJhN2IzODgzZmZkYTk1NzIzODdmZTQ0MzBjZjNiNzMxYjVlLnppcEw+5RDDwNmM8lIRxJgWoqeziD/9qVcjh/5EMM87cxteAQIAAAABAAAAAQAAAA==" + } + ], + "ramOps": [ + { + "operation": "OPERATION_PRIMARY_INDEX_ADD", + "payer": "eosio.nft.ft", + "delta": "136", + "usage": "999028", + "namespace": "NAMESPACE_TABLE_ROW", + "uniqueKey": "eosio.nft.ft:iy1bd2pu3ll4:token.a:...........bf", + "action": "ACTION_ADD" + } + ], + "rlimitOps": [ + { + "operation": "OPERATION_UPDATE", + "accountUsage": { + "owner": "ultra.nft.ft", + "netUsage": { + "lastOrdinal": 1398938303, + "valueEx": "6044", + "consumed": "353" + }, + "cpuUsage": { + "lastOrdinal": 1398938303, + "valueEx": "2279", + "consumed": "133" + }, + "ramUsage": "19360" + } + } + ], + "creationTree": [ + { + "creatorActionIndex": -1 + } + ] + } + ], + "unfilteredTransactionTraceCount": 2, + "filteredTransactionTraceCount": 1, + "unfilteredExecutedInputActionCount": 2, + "filteredExecutedInputActionCount": 1, + "unfilteredExecutedTotalActionCount": 2, + "filteredExecutedTotalActionCount": 1, + "validBlockSigningAuthorityV2": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5FgTg6xtmMaonVo3uo4S8oLTWTEUnSagAMQCvUkczu9e5hXaWp", + "weight": 1 + } + ] + } + }, + "activeScheduleV2": { + "version": 23, + "producers": [ + { + "accountName": "eosriobrazil", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS7nq54FcLXtDtY8GHqknYk5NqZPZNBVY3wCJQeRrzVzVjyXsEM6", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "cryptolions1", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS8LioNSB3ugUG7LweRBB2BR8AMJUEFGicXMs9msHByd9vMmxEKe", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "eosubisoft1", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5FgTg6xtmMaonVo3uo4S8oLTWTEUnSagAMQCvUkczu9e5hXaWp", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "uosswedenorg", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS6j7tqvStvcykc1ysDuNY1J8PqyARpGqWrxbVjiuNBQL8uL7r4A", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "eosnationftw", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5uMMtMvBp4pPEBiUsfdBR9LVnJnzUonGFvQtwnh1eoXzZZKN8U", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "ivote4eosusa", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5gKJLRWxMgLWDsvKu7YaTweoQpqin5SSzJ4mDuaXnQ6tZb1F31", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "uoseouldotio", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS7Tauu774Uz1bisTGUC37NsS6mxYoNaW3QKYF3oD1bUUJdCGxKf", + "weight": 1 + } + ] + } + } + } + ] + }, + "filteringApplied": true, + "filteringIncludeFilterExpr": "executed \u0026\u0026 (action==\"create\" || action==\"issue\") \u0026\u0026 account==\"eosio.nft.ft\" \u0026\u0026 receiver==\"eosio.nft.ft\"" +} \ No newline at end of file diff --git a/testdata/block-48098380.pb.json b/testdata/block-48098380.pb.json new file mode 100644 index 0000000..dbb64e1 --- /dev/null +++ b/testdata/block-48098380.pb.json @@ -0,0 +1,475 @@ +{ + "id": "02ddec4c819ca34314b3b424b4490a2dbcff4a4eefc5639972d19d057c1f82df", + "number": 48098380, + "version": 1, + "header": { + "timestamp": "2022-03-01T17:01:02.500Z", + "producer": "eosriobrazil", + "previous": "02ddec4b4f62d1752dd5a1a0e659c8d92de00634cbf763852232956d506c36df", + "transactionMroot": "GKv5yJEnE3bv/tEDdNra4rz9GAscg/FcxP4NG28kgnA=", + "actionMroot": "AepyB4EgMDgBln2pusotEhAHWzwwmXcDRhYQ1qDYkT4=", + "scheduleVersion": 23 + }, + "producerSignature": "SIG_K1_K9WAhTasCo4aSjs4NyNntxUXZHN7Gp6r8iGjps1bGoGMnaPVrE4awLWKFVWfsfAUrgBfwKCtTK1jdgnmyvdgRsu8u3SHaW", + "dposProposedIrreversibleBlocknum": 48098338, + "dposIrreversibleBlocknum": 48098290, + "blockrootMerkle": { + "nodeCount": 48098379, + "activeNodes": [ + "At3sS09i0XUt1aGg5lnI2S3gBjTL92OFIjKVbVBsNt8=", + "z9MZ2CLqSMgp4Ejx7fXWXjHID+sP1Epy+KRDJIVabto=", + "CJFruzqkY0sT6BtiABNVAAmkgC6dDkR2ycWBWieftVQ=", + "0Arft6iEpTZa1pPNENaMmBYjyBSX/BhOXCXIHQg8O3w=", + "nc6wMwN4xzHTOR/M95lAcp+rxIUWD5jFFyMrIlYv/qY=", + "KsNOi/KJUDtLRRYtZMUynrqCvCBTZpvn5Rrqg65+iiQ=", + "qTWFwR+p9/TkeYt8KoCLNu1Jv94d/6ipOBFVp4yDFZA=", + "v5rTH0uYOvtyTwJUyEfam9XSo+8eEVBYhzbT8AHvlAg=", + "e3nHGvHG6E4Q7Fv3N49457xqqPNs02rGOUzvt1Chfec=", + "mL/7WvV7cUim2VCMPXTUgDxu3qbwE5wfRocunccKUVI=", + "OGlksAVtDt6YHRW0ZjY2kTTLJm6Pvn3L074WhwEL86I=", + "26MQqrPolqpx0fCT0+3PAvI08etghktIGDIRP4IGF00=", + "jJyWo+En9HO6mRnTN+hnXxcPAp6Z5pwcBus4TULDTVw=", + "nS1/W7dtTpB3lxndStiQsUGxUPV0e0vH7vepD/OYnZ0=", + "xo8U7wXIJsMirafYsrkiRLxlodnsyrVJ0JKrfk+bWBg=", + "nzFxSRYd5vBXIeFSpUbwbrXUjdfWtkY0nPEySxAO9Ko=", + "z7DHvIVpwvPm7W2qidICYmTohMrmXOEJc7s8Ay+L2Fg=" + ] + }, + "producerToLastProduced": [ + { + "name": "cryptolions1", + "lastBlockNumProduced": 48098314 + }, + { + "name": "eosnationftw", + "lastBlockNumProduced": 48098350 + }, + { + "name": "eosriobrazil", + "lastBlockNumProduced": 48098380 + }, + { + "name": "eosubisoft1", + "lastBlockNumProduced": 48098326 + }, + { + "name": "ivote4eosusa", + "lastBlockNumProduced": 48098362 + }, + { + "name": "uoseouldotio", + "lastBlockNumProduced": 48098374 + }, + { + "name": "uosswedenorg", + "lastBlockNumProduced": 48098338 + } + ], + "producerToLastImpliedIrb": [ + { + "name": "cryptolions1", + "lastBlockNumProduced": 48098266 + }, + { + "name": "eosnationftw", + "lastBlockNumProduced": 48098302 + }, + { + "name": "eosriobrazil", + "lastBlockNumProduced": 48098338 + }, + { + "name": "eosubisoft1", + "lastBlockNumProduced": 48098278 + }, + { + "name": "ivote4eosusa", + "lastBlockNumProduced": 48098314 + }, + { + "name": "uoseouldotio", + "lastBlockNumProduced": 48098326 + }, + { + "name": "uosswedenorg", + "lastBlockNumProduced": 48098290 + } + ], + "confirmCount": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 4, + 4 + ], + "pendingSchedule": { + "scheduleLibNum": 31206479, + "scheduleHash": "0kV64BEe9ZeDfHNPg++pnKzQpRty/TWNE0fduNUrC6o=", + "scheduleV2": { + "version": 23 + } + }, + "activatedProtocolFeatures": { + "protocolFeatures": [ + "DsfggBd7LAKyeNUIhhFoa0nXOZJaktm/ys1/xrdAU70=", + "GpmlnYfgbgnsWwKKnLt3SbSlrYgZAENl0C3EN5qLckE=", + "JlL1+WAGKUEJs90LveY2k/VTJK9FK3me4TeoGpBe7SU=", + "KZ3LavaSMkuJmznxbVpTCjMGKATkHwncl+nxVrRHZwc=", + "SpDADVVFTcWwWQVcohNXnG6oVpZ3EqVgF0h4hqTUzA8=", + "TnvzSNoAqUVImypoF0nrVvXeALkAAU4Tfdrjn0j2nWc=", + "T8qL2Cu9GB5xTig/g+G0XZXKWvQPuJrTl3tlPESPeMI=", + "aNyqNMBRfRlmbmszrdZzUdjF9p6ZnKHjeTG8QQopdCg=", + "i6Uv56OVbFzTplajF0uTHTuyq7RVeL78WfKD7NgWpAU=", + "rZ49j2UGh3Cf1o9LkLQffYJaNlsCwjpjbO+IrCrADEM=", + "4PtksQhcxVOJcBWNBaAJwk4nb7lOGgv2pSi0j7xP9SY=", + "70MRLGVDuI2yKDouB3J4wxWuLIRxmosl8lzIhWX76pk=", + "8K9W0sWkjWCkpbXJA+37fbOnNqlO1YnQt5ffM/+dPh0=" + ] + }, + "rlimitOps": [ + { + "operation": "OPERATION_UPDATE", + "state": { + "averageBlockNetUsage": { + "lastOrdinal": 48098379, + "valueEx": "445643674", + "consumed": "1057" + }, + "averageBlockCpuUsage": { + "lastOrdinal": 48098379, + "valueEx": "165093170", + "consumed": "389" + }, + "pendingNetUsage": "360", + "pendingCpuUsage": "133", + "totalNetWeight": "101000000", + "totalCpuWeight": "101000000", + "totalRamBytes": "217146944", + "virtualNetLimit": "1048576", + "virtualCpuLimit": "400000" + } + }, + { + "operation": "OPERATION_UPDATE", + "state": { + "averageBlockNetUsage": { + "lastOrdinal": 48098380, + "valueEx": "444929976", + "consumed": "802" + }, + "averageBlockCpuUsage": { + "lastOrdinal": 48098380, + "valueEx": "164825727", + "consumed": "297" + }, + "totalNetWeight": "101000000", + "totalCpuWeight": "101000000", + "totalRamBytes": "217146944", + "virtualNetLimit": "1048576", + "virtualCpuLimit": "400000" + } + } + ], + "filteredTransactions": [ + { + "id": "75a414d9b1c620b190616f9583b2b6ff57bbc13b3eae31e8bc443c6f45813e13", + "status": "TRANSACTIONSTATUS_EXECUTED", + "cpuUsageMicroSeconds": 132, + "netUsageWords": 44, + "packedTransaction": { + "signatures": [ + "SIG_K1_KV6ETUXQiNssV9h2vg2is3w8tMmn2azof1y8gAdvJQAkajDE98u5mS8WHGfiS5KoiiFjVnuYoP1yrYGdTuAj8b1dJHVVqv" + ], + "packedTransaction": "wVEeYkDsDi+65QAAAAABkBfIawLqMFUAAAAAAKUxdgGQF8hrAnNz1AAAACBNBZE5NgFAYhy6inSCdwEBLAAAAAAAAAABAAAABG51bGwBGDYyMWU1MTQ4Yzc5MzgxMDNiZmRkNjY0OQA=" + } + } + ], + "unfilteredTransactionCount": 1, + "filteredTransactionCount": 1, + "filteredTransactionTraces": [ + { + "id": "75a414d9b1c620b190616f9583b2b6ff57bbc13b3eae31e8bc443c6f45813e13", + "blockNum": "48098380", + "index": "1", + "blockTime": "2022-03-01T17:01:02.500Z", + "producerBlockId": "02ddec4c819ca34314b3b424b4490a2dbcff4a4eefc5639972d19d057c1f82df", + "receipt": { + "status": "TRANSACTIONSTATUS_EXECUTED", + "cpuUsageMicroSeconds": 132, + "netUsageWords": 44 + }, + "elapsed": "199", + "netUsage": "352", + "actionTraces": [ + { + "receiver": "eosio.nft.ft", + "receipt": { + "receiver": "eosio.nft.ft", + "digest": "a8b7ba3e0a3850a0bc7453d72faeffc85ffc393a4e95b37e7f450fa535da8f06", + "globalSequence": "125831990", + "authSequence": [ + { + "accountName": "ultra.nft.ft", + "sequence": "189" + } + ], + "recvSequence": "216", + "codeSequence": "5", + "abiSequence": "5" + }, + "action": { + "account": "eosio.nft.ft", + "name": "issue", + "authorization": [ + { + "actor": "ultra.nft.ft", + "permission": "backend" + } + ], + "jsonData": "{\"issue\":{\"memo\":\"621e5148c7938103bfdd6649\",\"to\":\"iy1bd2pu3ll4\",\"token_configs\":[{\"amount\":1,\"custom_data\":\"null\",\"token_factory_id\":44}]}}", + "rawData": "AUBiHLqKdIJ3AQEsAAAAAAAAAAEAAAAEbnVsbAEYNjIxZTUxNDhjNzkzODEwM2JmZGQ2NjQ5" + }, + "elapsed": "161", + "transactionId": "75a414d9b1c620b190616f9583b2b6ff57bbc13b3eae31e8bc443c6f45813e13", + "blockNum": "48098380", + "producerBlockId": "02ddec4c819ca34314b3b424b4490a2dbcff4a4eefc5639972d19d057c1f82df", + "blockTime": "2022-03-01T17:01:02.500Z", + "accountRamDeltas": [ + { + "account": "eosio.nft.ft", + "delta": "136" + } + ], + "actionOrdinal": 1, + "filteringMatched": true + } + ], + "dbOps": [ + { + "operation": "OPERATION_UPDATE", + "code": "eosio.nft.ft", + "tableName": "next.token", + "primaryKey": "next.token", + "oldPayer": "eosio.nft.ft", + "newPayer": "eosio.nft.ft", + "oldData": "fAAAAAAAAAA=", + "newData": "fQAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "eosio.nft.ft", + "scope": "iy1bd2pu3ll4", + "tableName": "token.a", + "primaryKey": "...........bg", + "newPayer": "eosio.nft.ft", + "newData": "fAAAAAAAAAAsAAAAAAAAAE5RHmICAAAA" + }, + { + "operation": "OPERATION_UPDATE", + "code": "eosio.nft.ft", + "scope": "eosio.nft.ft", + "tableName": "factory.a", + "primaryKey": "...........2g", + "oldPayer": "ultra.nft.ft", + "newPayer": "ultra.nft.ft", + "oldData": "LAAAAAAAAACQF8hrAnNz1JAXyGsCc3PUAAAAAAAAAAAAAOH1BQAAAAAIVU9TAAAAAAGQF8hrAnNz1OgDAAAAAAEAAAAAAAEAAAAAAZAXyGsCc3PUAAF8aHR0cHM6Ly9zMy51cy1lYXN0LTEud2FzYWJpc3lzLmNvbS91bHRyYWlvLXVuaXEtc3RhZ2luZy80YzNlZTUxMGMzYzBkOThjZjI1MjExYzQ5ODE2YTJhN2IzODgzZmZkYTk1NzIzODdmZTQ0MzBjZjNiNzMxYjVlLnppcEw+5RDDwNmM8lIRxJgWoqeziD/9qVcjh/5EMM87cxteAQIAAAABAAAAAQAAAA==", + "newData": "LAAAAAAAAACQF8hrAnNz1JAXyGsCc3PUAAAAAAAAAAAAAOH1BQAAAAAIVU9TAAAAAAGQF8hrAnNz1OgDAAAAAAEAAAAAAAEAAAAAAZAXyGsCc3PUAAF8aHR0cHM6Ly9zMy51cy1lYXN0LTEud2FzYWJpc3lzLmNvbS91bHRyYWlvLXVuaXEtc3RhZ2luZy80YzNlZTUxMGMzYzBkOThjZjI1MjExYzQ5ODE2YTJhN2IzODgzZmZkYTk1NzIzODdmZTQ0MzBjZjNiNzMxYjVlLnppcEw+5RDDwNmM8lIRxJgWoqeziD/9qVcjh/5EMM87cxteAQIAAAACAAAAAgAAAA==" + } + ], + "ramOps": [ + { + "operation": "OPERATION_PRIMARY_INDEX_ADD", + "payer": "eosio.nft.ft", + "delta": "136", + "usage": "999164", + "namespace": "NAMESPACE_TABLE_ROW", + "uniqueKey": "eosio.nft.ft:iy1bd2pu3ll4:token.a:...........bg", + "action": "ACTION_ADD" + } + ], + "rlimitOps": [ + { + "operation": "OPERATION_UPDATE", + "accountUsage": { + "owner": "ultra.nft.ft", + "netUsage": { + "lastOrdinal": 1398938525, + "valueEx": "8074", + "consumed": "353" + }, + "cpuUsage": { + "lastOrdinal": 1398938525, + "valueEx": "3040", + "consumed": "133" + }, + "ramUsage": "19360" + } + } + ], + "creationTree": [ + { + "creatorActionIndex": -1 + } + ] + } + ], + "unfilteredTransactionTraceCount": 2, + "filteredTransactionTraceCount": 1, + "unfilteredExecutedInputActionCount": 2, + "filteredExecutedInputActionCount": 1, + "unfilteredExecutedTotalActionCount": 2, + "filteredExecutedTotalActionCount": 1, + "validBlockSigningAuthorityV2": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS7nq54FcLXtDtY8GHqknYk5NqZPZNBVY3wCJQeRrzVzVjyXsEM6", + "weight": 1 + } + ] + } + }, + "activeScheduleV2": { + "version": 23, + "producers": [ + { + "accountName": "eosriobrazil", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS7nq54FcLXtDtY8GHqknYk5NqZPZNBVY3wCJQeRrzVzVjyXsEM6", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "cryptolions1", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS8LioNSB3ugUG7LweRBB2BR8AMJUEFGicXMs9msHByd9vMmxEKe", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "eosubisoft1", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5FgTg6xtmMaonVo3uo4S8oLTWTEUnSagAMQCvUkczu9e5hXaWp", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "uosswedenorg", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS6j7tqvStvcykc1ysDuNY1J8PqyARpGqWrxbVjiuNBQL8uL7r4A", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "eosnationftw", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5uMMtMvBp4pPEBiUsfdBR9LVnJnzUonGFvQtwnh1eoXzZZKN8U", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "ivote4eosusa", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS5gKJLRWxMgLWDsvKu7YaTweoQpqin5SSzJ4mDuaXnQ6tZb1F31", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "uoseouldotio", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS7Tauu774Uz1bisTGUC37NsS6mxYoNaW3QKYF3oD1bUUJdCGxKf", + "weight": 1 + } + ] + } + } + } + ] + }, + "filteringApplied": true, + "filteringIncludeFilterExpr": "executed \u0026\u0026 (action==\"create\" || action==\"issue\") \u0026\u0026 account==\"eosio.nft.ft\" \u0026\u0026 receiver==\"eosio.nft.ft\"" +} \ No newline at end of file diff --git a/testdata/block-49608395.pb.json b/testdata/block-49608395.pb.json new file mode 100644 index 0000000..e5b2de9 --- /dev/null +++ b/testdata/block-49608395.pb.json @@ -0,0 +1 @@ +{"id":"02f4f6cb38b5379b29771b233cb4b53cb8b3a7d6c005e894ec0299f2e66a5288","number":49608395,"version":1,"header":{"timestamp":"2022-03-10T10:44:30Z","producer":"uosswedenorg","confirmed":72,"previous":"02f4f6cab2ec4e3c6e4cf3334700b2e395324b0e78593e85aa5b89e7999247c5","transactionMroot":"f5NkT7Nf7bmfsqIi2VrE0Hs7f9RlTs2gr1i913N4TH4=","actionMroot":"g17v2TC6rStvR22850e9AuzrTVDlzk2T1lYAsyQigGc=","scheduleVersion":23},"producerSignature":"SIG_K1_K8zK3HPjNXjCorqGRWftJTZCGbqYWj331zzJW6s9AsR56zW3FUuYWQ4xNZBPLbDbAAh4z3r9fMB1gAeJy9n9TK4KNNi3MB","dposProposedIrreversibleBlocknum":49608358,"dposIrreversibleBlocknum":49608310,"blockrootMerkle":{"nodeCount":49608394,"activeNodes":["ox9B9UeNwaBh0sYu+CsIi0+1C2WNp3rI1ylLvX/2RI4=","fRACHBSl+q/HtpIqDeCf6FUItUHZ1VKbboVisoLxV2M=","W0Ajr5A33UATt2rv882YvW681a3OgSE7LOqNi9V/h0U=","qYCtClFVIEPY3Cz4MdNgpYaEwskx3N/HJ/dUEguQ82M=","h8vaseleis7EnQoXWD2B5mHdTu5hcwVVe4GulbKpk48=","Yx3KJQL7yrTaMv2ZrchE4qc/WbCYyFmFlQCFt6dscE8=","0SPl7ZctdkgqkTK2zngv4J8bHEBsseIDUfU4AKhgxro=","EU1cs5EqE6BkEtQO8uvb0alF9nRUrvnAaNebrwKsf5w=","KizigjOM6bpEU81uQc6P9ZmNvPLlZwogeF4UyXfGQBA=","XS6lffto76UJ8SRhRTVQwE7JA7mBz/CzknmT9uybw04=","R05JWag4ZqATe11qzqG3J2DXyitLeufAP7CihlRRPWg=","rhv1w2khB0AWwRNy+YPz0pD4jtx3qWpGRsPz2cvzloI=","0x4+L577oGT2S75ee3Z6MGEMlhXtq6MIrtCvXurS8ZQ=","nS1/W7dtTpB3lxndStiQsUGxUPV0e0vH7vepD/OYnZ0=","xo8U7wXIJsMirafYsrkiRLxlodnsyrVJ0JKrfk+bWBg=","nzFxSRYd5vBXIeFSpUbwbrXUjdfWtkY0nPEySxAO9Ko=","tJ9mtcEclXSO0QQjpk5y5GLcdH8DYmg3mJxxGbF9Q6k="]},"producerToLastProduced":[{"name":"cryptolions1","lastBlockNumProduced":49608382},{"name":"eosnationftw","lastBlockNumProduced":49608334},{"name":"eosriobrazil","lastBlockNumProduced":49608370},{"name":"eosubisoft1","lastBlockNumProduced":49608394},{"name":"ivote4eosusa","lastBlockNumProduced":49608346},{"name":"uoseouldotio","lastBlockNumProduced":49608358},{"name":"uosswedenorg","lastBlockNumProduced":49608395}],"producerToLastImpliedIrb":[{"name":"cryptolions1","lastBlockNumProduced":49608334},{"name":"eosnationftw","lastBlockNumProduced":49608286},{"name":"eosriobrazil","lastBlockNumProduced":49608322},{"name":"eosubisoft1","lastBlockNumProduced":49608346},{"name":"ivote4eosusa","lastBlockNumProduced":49608298},{"name":"uoseouldotio","lastBlockNumProduced":49608310},{"name":"uosswedenorg","lastBlockNumProduced":49608346}],"confirmCount":[1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,4],"pendingSchedule":{"scheduleLibNum":31206479,"scheduleHash":"0kV64BEe9ZeDfHNPg++pnKzQpRty/TWNE0fduNUrC6o=","scheduleV2":{"version":23}},"activatedProtocolFeatures":{"protocolFeatures":["DsfggBd7LAKyeNUIhhFoa0nXOZJaktm/ys1/xrdAU70=","GpmlnYfgbgnsWwKKnLt3SbSlrYgZAENl0C3EN5qLckE=","JlL1+WAGKUEJs90LveY2k/VTJK9FK3me4TeoGpBe7SU=","KZ3LavaSMkuJmznxbVpTCjMGKATkHwncl+nxVrRHZwc=","SpDADVVFTcWwWQVcohNXnG6oVpZ3EqVgF0h4hqTUzA8=","TnvzSNoAqUVImypoF0nrVvXeALkAAU4Tfdrjn0j2nWc=","T8qL2Cu9GB5xTig/g+G0XZXKWvQPuJrTl3tlPESPeMI=","aNyqNMBRfRlmbmszrdZzUdjF9p6ZnKHjeTG8QQopdCg=","i6Uv56OVbFzTplajF0uTHTuyq7RVeL78WfKD7NgWpAU=","rZ49j2UGh3Cf1o9LkLQffYJaNlsCwjpjbO+IrCrADEM=","4PtksQhcxVOJcBWNBaAJwk4nb7lOGgv2pSi0j7xP9SY=","70MRLGVDuI2yKDouB3J4wxWuLIRxmosl8lzIhWX76pk=","8K9W0sWkjWCkpbXJA+37fbOnNqlO1YnQt5ffM/+dPh0="]},"rlimitOps":[{"operation":"OPERATION_UPDATE","state":{"averageBlockNetUsage":{"lastOrdinal":49608394,"valueEx":"471301668","consumed":"480"},"averageBlockCpuUsage":{"lastOrdinal":49608394,"valueEx":"174923168","consumed":"176"},"pendingNetUsage":"36296","pendingCpuUsage":"13837","totalNetWeight":"101000000","totalCpuWeight":"101000000","totalRamBytes":"217146944","virtualNetLimit":"1048576","virtualCpuLimit":"400000"}},{"operation":"OPERATION_UPDATE","state":{"averageBlockNetUsage":{"lastOrdinal":49608395,"valueEx":"769840821","consumed":"36764"},"averageBlockCpuUsage":{"lastOrdinal":49608395,"valueEx":"288773808","consumed":"14011"},"totalNetWeight":"101000000","totalCpuWeight":"101000000","totalRamBytes":"217146944","virtualNetLimit":"1048576","virtualCpuLimit":"400000"}}],"filteredTransactions":[{"id":"52f59e9a831a3e2c318f2fe6a5ae3eec93ef582c7acbeaa647ab76849b328206","index":"4","status":"TRANSACTIONSTATUS_EXECUTED","cpuUsageMicroSeconds":13502,"netUsageWords":4425,"packedTransaction":{"signatures":["SIG_K1_KeqKDi1p6UyC8pAk3WNGoPyCn62HXHWKtGHQBKZKxQcT3DqLMLmJYvdyMDFGiWPX4aaZCqaZaaxbbSus7EHq4RFuyTZFsm"],"packedTransaction":"qtYpYmr2G7aG1gAAAAACAKaCNAPqMFUAAABXLTzNzQEAQqW3AnNz1AAAAACo7TIyLABCpbcCc3PUAKjDVAFzc9QBAAAAAAAAAAhVT1MAAAAAC3Rlc3QgdG9rZW5zAHCkNANzc9QAAFDZRHUvRQEAQqW3AnNz1AAAAACo7TIyLQBCpbcCc3PUJGVkMTkxOTFiLTM5NjItNGM1OC05ZGVlLWY0MTM5ODg2NmVlMQA="}}],"unfilteredTransactionCount":5,"filteredTransactionCount":1,"filteredTransactionTraces":[{"id":"52f59e9a831a3e2c318f2fe6a5ae3eec93ef582c7acbeaa647ab76849b328206","blockNum":"49608395","index":"5","blockTime":"2022-03-10T10:44:30Z","producerBlockId":"02f4f6cb38b5379b29771b233cb4b53cb8b3a7d6c005e894ec0299f2e66a5288","receipt":{"status":"TRANSACTIONSTATUS_EXECUTED","cpuUsageMicroSeconds":13502,"netUsageWords":4425},"elapsed":"464","netUsage":"35400","actionTraces":[{"receiver":"eosio.token","receipt":{"receiver":"eosio.token","digest":"b3747e106bd73dc4cb2f48b7b62c7b2f4027994d27ef947bc2131218abc44a2d","globalSequence":"130257993","authSequence":[{"accountName":"ultra.prop1","sequence":"82"}],"recvSequence":"8536","codeSequence":"1","abiSequence":"1"},"action":{"account":"eosio.token","name":"transfer","authorization":[{"actor":"ultra.prop1","permission":"active"}],"jsonData":"{\"from\":\"ultra.prop1\",\"to\":\"ultra.eosio\",\"quantity\":\"0.00000001 UOS\",\"memo\":\"test tokens\"}","rawData":"AEKltwJzc9QAqMNUAXNz1AEAAAAAAAAACFVPUwAAAAALdGVzdCB0b2tlbnM="},"elapsed":"62","transactionId":"52f59e9a831a3e2c318f2fe6a5ae3eec93ef582c7acbeaa647ab76849b328206","blockNum":"49608395","producerBlockId":"02f4f6cb38b5379b29771b233cb4b53cb8b3a7d6c005e894ec0299f2e66a5288","blockTime":"2022-03-10T10:44:30Z","actionOrdinal":1,"filteringMatched":true},{"receiver":"ultra.prop1","receipt":{"receiver":"ultra.prop1","digest":"b3747e106bd73dc4cb2f48b7b62c7b2f4027994d27ef947bc2131218abc44a2d","globalSequence":"130257994","authSequence":[{"accountName":"ultra.prop1","sequence":"83"}],"recvSequence":"2","codeSequence":"1","abiSequence":"1"},"action":{"account":"eosio.token","name":"transfer","authorization":[{"actor":"ultra.prop1","permission":"active"}],"jsonData":"{\"from\":\"ultra.prop1\",\"to\":\"ultra.eosio\",\"quantity\":\"0.00000001 UOS\",\"memo\":\"test tokens\"}","rawData":"AEKltwJzc9QAqMNUAXNz1AEAAAAAAAAACFVPUwAAAAALdGVzdCB0b2tlbnM="},"elapsed":"1","transactionId":"52f59e9a831a3e2c318f2fe6a5ae3eec93ef582c7acbeaa647ab76849b328206","blockNum":"49608395","producerBlockId":"02f4f6cb38b5379b29771b233cb4b53cb8b3a7d6c005e894ec0299f2e66a5288","blockTime":"2022-03-10T10:44:30Z","actionOrdinal":3,"creatorActionOrdinal":1,"closestUnnotifiedAncestorActionOrdinal":1,"executionIndex":1},{"receiver":"ultra.eosio","receipt":{"receiver":"ultra.eosio","digest":"b3747e106bd73dc4cb2f48b7b62c7b2f4027994d27ef947bc2131218abc44a2d","globalSequence":"130257995","authSequence":[{"accountName":"ultra.prop1","sequence":"84"}],"recvSequence":"18","codeSequence":"1","abiSequence":"1"},"action":{"account":"eosio.token","name":"transfer","authorization":[{"actor":"ultra.prop1","permission":"active"}],"jsonData":"{\"from\":\"ultra.prop1\",\"to\":\"ultra.eosio\",\"quantity\":\"0.00000001 UOS\",\"memo\":\"test tokens\"}","rawData":"AEKltwJzc9QAqMNUAXNz1AEAAAAAAAAACFVPUwAAAAALdGVzdCB0b2tlbnM="},"elapsed":"2","transactionId":"52f59e9a831a3e2c318f2fe6a5ae3eec93ef582c7acbeaa647ab76849b328206","blockNum":"49608395","producerBlockId":"02f4f6cb38b5379b29771b233cb4b53cb8b3a7d6c005e894ec0299f2e66a5288","blockTime":"2022-03-10T10:44:30Z","actionOrdinal":4,"creatorActionOrdinal":1,"closestUnnotifiedAncestorActionOrdinal":1,"executionIndex":2},{"receiver":"ultra.tools","receipt":{"receiver":"ultra.tools","digest":"1ddb6712455737ebd42b32b63a7bd25e965ae011348b609f5e8a652aeecde785","globalSequence":"130257996","authSequence":[{"accountName":"ultra.prop1","sequence":"85"}],"recvSequence":"3","codeSequence":"2","abiSequence":"2"},"action":{"account":"ultra.tools","name":"correlate","authorization":[{"actor":"ultra.prop1","permission":"active"}],"jsonData":"{\"correlation_id\":\"ed19191b-3962-4c58-9dee-f41398866ee1\",\"payer\":\"ultra.prop1\"}","rawData":"AEKltwJzc9QkZWQxOTE5MWItMzk2Mi00YzU4LTlkZWUtZjQxMzk4ODY2ZWUx"},"elapsed":"350","transactionId":"52f59e9a831a3e2c318f2fe6a5ae3eec93ef582c7acbeaa647ab76849b328206","blockNum":"49608395","producerBlockId":"02f4f6cb38b5379b29771b233cb4b53cb8b3a7d6c005e894ec0299f2e66a5288","blockTime":"2022-03-10T10:44:30Z","accountRamDeltas":[{"account":"ultra.prop1"}],"actionOrdinal":2,"executionIndex":3}],"dbOps":[{"operation":"OPERATION_UPDATE","code":"eosio.token","scope":"ultra.prop1","tableName":"accounts","primaryKey":"........ehbp5","oldPayer":"eosio","newPayer":"eosio","oldData":"AOH1BQAAAAAIVU9TAAAAAA==","newData":"/+D1BQAAAAAIVU9TAAAAAA=="},{"operation":"OPERATION_UPDATE","code":"eosio.token","scope":"ultra.eosio","tableName":"accounts","primaryKey":"........ehbp5","oldPayer":"eosio","newPayer":"eosio","oldData":"AAivLwAAAAAIVU9TAAAAAA==","newData":"AQivLwAAAAAIVU9TAAAAAA=="},{"operation":"OPERATION_REMOVE","actionIndex":3,"code":"ultra.tools","scope":"ultra.prop1","tableName":"corrids","primaryKey":"gcsnmgdta5ma5","oldPayer":"ultra.prop1","oldData":"ZWQxOTE5MWIcLTM5NjItNGM1OC05ZGVlLWY0MTM5ODg2NmVlMcijBVM="},{"operation":"OPERATION_INSERT","actionIndex":3,"code":"ultra.tools","scope":"ultra.prop1","tableName":"corrids","primaryKey":"gcsnmgdta5ma5","newPayer":"ultra.prop1","newData":"ZWQxOTE5MWIcLTM5NjItNGM1OC05ZGVlLWY0MTM5ODg2NmVlMRwmeVM="}],"ramOps":[{"operation":"OPERATION_PRIMARY_INDEX_REMOVE","actionIndex":3,"payer":"ultra.prop1","delta":"-153","usage":"3055","namespace":"NAMESPACE_TABLE_ROW","uniqueKey":"ultra.tools:ultra.prop1:corrids:gcsnmgdta5ma5","action":"ACTION_REMOVE"},{"operation":"OPERATION_SECONDARY_INDEX_REMOVE","actionIndex":3,"payer":"ultra.prop1","delta":"-128","usage":"2927","namespace":"NAMESPACE_SECONDARY_INDEX","uniqueKey":"ultra.tools:ultra.prop1:corrids:gcsnmgdta5ma5","action":"ACTION_REMOVE"},{"operation":"OPERATION_REMOVE_TABLE","actionIndex":3,"payer":"ultra.prop1","delta":"-112","usage":"2815","namespace":"NAMESPACE_TABLE","uniqueKey":"ultra.tools:ultra.prop1:corrids","action":"ACTION_REMOVE"},{"operation":"OPERATION_CREATE_TABLE","actionIndex":3,"payer":"ultra.prop1","delta":"112","usage":"2927","namespace":"NAMESPACE_TABLE","uniqueKey":"ultra.tools:ultra.prop1:corrids","action":"ACTION_ADD"},{"operation":"OPERATION_PRIMARY_INDEX_ADD","actionIndex":3,"payer":"ultra.prop1","delta":"153","usage":"3080","namespace":"NAMESPACE_TABLE_ROW","uniqueKey":"ultra.tools:ultra.prop1:corrids:gcsnmgdta5ma5","action":"ACTION_ADD"},{"operation":"OPERATION_SECONDARY_INDEX_ADD","actionIndex":3,"payer":"ultra.prop1","delta":"128","usage":"3208","namespace":"NAMESPACE_SECONDARY_INDEX","uniqueKey":"ultra.tools:ultra.prop1:corrids:gcsnmgdta5ma5","action":"ACTION_ADD"}],"rlimitOps":[{"operation":"OPERATION_UPDATE","accountUsage":{"owner":"ultra.prop1","netUsage":{"lastOrdinal":1400448540,"valueEx":"204862","consumed":"35400"},"cpuUsage":{"lastOrdinal":1400448540,"valueEx":"78137","consumed":"13502"},"ramUsage":"3208"}}],"tableOps":[{"operation":"OPERATION_REMOVE","actionIndex":3,"payer":"ultra.prop1","code":"ultra.tools","scope":"ultra.prop1","tableName":"corrids"},{"operation":"OPERATION_INSERT","actionIndex":3,"payer":"ultra.prop1","code":"ultra.tools","scope":"ultra.prop1","tableName":"corrids"}],"creationTree":[{"creatorActionIndex":-1},{"executionActionIndex":1},{"executionActionIndex":2},{"creatorActionIndex":-1,"executionActionIndex":3}]}],"unfilteredTransactionTraceCount":6,"filteredTransactionTraceCount":1,"unfilteredExecutedInputActionCount":7,"filteredExecutedInputActionCount":1,"unfilteredExecutedTotalActionCount":9,"filteredExecutedTotalActionCount":1,"validBlockSigningAuthorityV2":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS6j7tqvStvcykc1ysDuNY1J8PqyARpGqWrxbVjiuNBQL8uL7r4A","weight":1}]}},"activeScheduleV2":{"version":23,"producers":[{"accountName":"eosriobrazil","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS7nq54FcLXtDtY8GHqknYk5NqZPZNBVY3wCJQeRrzVzVjyXsEM6","weight":1}]}}},{"accountName":"cryptolions1","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS8LioNSB3ugUG7LweRBB2BR8AMJUEFGicXMs9msHByd9vMmxEKe","weight":1}]}}},{"accountName":"eosubisoft1","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS5FgTg6xtmMaonVo3uo4S8oLTWTEUnSagAMQCvUkczu9e5hXaWp","weight":1}]}}},{"accountName":"uosswedenorg","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS6j7tqvStvcykc1ysDuNY1J8PqyARpGqWrxbVjiuNBQL8uL7r4A","weight":1}]}}},{"accountName":"eosnationftw","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS5uMMtMvBp4pPEBiUsfdBR9LVnJnzUonGFvQtwnh1eoXzZZKN8U","weight":1}]}}},{"accountName":"ivote4eosusa","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS5gKJLRWxMgLWDsvKu7YaTweoQpqin5SSzJ4mDuaXnQ6tZb1F31","weight":1}]}}},{"accountName":"uoseouldotio","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS7Tauu774Uz1bisTGUC37NsS6mxYoNaW3QKYF3oD1bUUJdCGxKf","weight":1}]}}}]},"filteringApplied":true,"filteringIncludeFilterExpr":"executed \u0026\u0026 action==\"transfer\" \u0026\u0026 account==\"eosio.token\" \u0026\u0026 receiver==\"eosio.token\""} \ No newline at end of file diff --git a/testdata/block-50705256.pb.json b/testdata/block-50705256.pb.json new file mode 100644 index 0000000..fe88020 --- /dev/null +++ b/testdata/block-50705256.pb.json @@ -0,0 +1 @@ +{"id":"0305b368694e048ae2ca71b22e1ead4af814e807e5d9dbf290f9da73eee8d780","number":50705256,"version":1,"header":{"timestamp":"2022-03-16T19:09:17Z","producer":"eosubisoft1","previous":"0305b3672217e643b2c14c34cb258ff9fd3faf15da53b3bf1332372ba4f0e0ba","transactionMroot":"tx8smLnP989Ntmr1hjp5WOeIJPZkZVmL0T9lJIuhwxQ=","actionMroot":"INgjr0AMyq9S+IJd2YkOYXZAtg+A1G1C+g7COkFuK5o=","scheduleVersion":23},"producerSignature":"SIG_K1_K95oMf7t5zMxBF6uX9tMrtbKq6Hk63YAvRoanHF4p8RhU42dHDP9bTGLiJtnrZBVbEfFNHEDozHPeU7EPJNDH1QuwPGt3T","dposProposedIrreversibleBlocknum":50705209,"dposIrreversibleBlocknum":50705161,"blockrootMerkle":{"nodeCount":50705255,"activeNodes":["AwWzZyIX5kOywUw0yyWP+f0/rxXaU7O/EzI3K6Tw4Lo=","QWNTA5517isotXxc31qpsY+z7ZgdncZEvwmhN9tpzIc=","CgYsiSvly9ycd67DZS93Ad5W5K/0TkNFOb8H5Gy88vs=","OvH9yI9iNbH7ZYMFyaU4u14vA9UyDKaa+LdwBSDouzg=","R/U7M5cpgKsjKoZl0WlEvvNOvAGcn8oPLRwu9EWFCJI=","UIoydemG0GOTjt9w95Zz9wBmOVQ7gP4Z8tUS/cG1Gm4=","F9NQfnSJB3T/hautW8MPI4jQ0744xaK0PcU0OWbA6Co=","gixqS6X27nQ06UFHmSbHPzMiATKpZIQioHzh/mpAPEY=","G3jZ3hWzeS6q8+hq58pldvn6dhShEYAjdIYY0rfN71o=","Zf71rGmdZF+fm1a1FHxYQy0ACA+ldrcO73AiMI/qS8o=","fM9+wrigjmN8Q79y/cbz4tB2vpql95ypegSqcI66BPo=","y9ryTtouC/uHFG9gQo1d5IAxqwR2KLKcQiSs/jo5sXw=","6xx6ZQ38Z/XlKVzMRbTR7NhVAkxwAD1NdyPNDmOKJlg=","nzFxSRYd5vBXIeFSpUbwbrXUjdfWtkY0nPEySxAO9Ko=","/rt9YJppHmlDVSaUhs989e0Erum2NJ7DeH0x/NxZM6s="]},"producerToLastProduced":[{"name":"cryptolions1","lastBlockNumProduced":50705245},{"name":"eosnationftw","lastBlockNumProduced":50705197},{"name":"eosriobrazil","lastBlockNumProduced":50705233},{"name":"eosubisoft1","lastBlockNumProduced":50705256},{"name":"ivote4eosusa","lastBlockNumProduced":50705209},{"name":"uoseouldotio","lastBlockNumProduced":50705221},{"name":"uosswedenorg","lastBlockNumProduced":50705185}],"producerToLastImpliedIrb":[{"name":"cryptolions1","lastBlockNumProduced":50705197},{"name":"eosnationftw","lastBlockNumProduced":50705149},{"name":"eosriobrazil","lastBlockNumProduced":50705185},{"name":"eosubisoft1","lastBlockNumProduced":50705209},{"name":"ivote4eosusa","lastBlockNumProduced":50705161},{"name":"uoseouldotio","lastBlockNumProduced":50705173},{"name":"uosswedenorg","lastBlockNumProduced":50705137}],"confirmCount":[1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4],"pendingSchedule":{"scheduleLibNum":31206479,"scheduleHash":"0kV64BEe9ZeDfHNPg++pnKzQpRty/TWNE0fduNUrC6o=","scheduleV2":{"version":23}},"activatedProtocolFeatures":{"protocolFeatures":["DsfggBd7LAKyeNUIhhFoa0nXOZJaktm/ys1/xrdAU70=","GpmlnYfgbgnsWwKKnLt3SbSlrYgZAENl0C3EN5qLckE=","JlL1+WAGKUEJs90LveY2k/VTJK9FK3me4TeoGpBe7SU=","KZ3LavaSMkuJmznxbVpTCjMGKATkHwncl+nxVrRHZwc=","SpDADVVFTcWwWQVcohNXnG6oVpZ3EqVgF0h4hqTUzA8=","TnvzSNoAqUVImypoF0nrVvXeALkAAU4Tfdrjn0j2nWc=","T8qL2Cu9GB5xTig/g+G0XZXKWvQPuJrTl3tlPESPeMI=","aNyqNMBRfRlmbmszrdZzUdjF9p6ZnKHjeTG8QQopdCg=","i6Uv56OVbFzTplajF0uTHTuyq7RVeL78WfKD7NgWpAU=","rZ49j2UGh3Cf1o9LkLQffYJaNlsCwjpjbO+IrCrADEM=","4PtksQhcxVOJcBWNBaAJwk4nb7lOGgv2pSi0j7xP9SY=","70MRLGVDuI2yKDouB3J4wxWuLIRxmosl8lzIhWX76pk=","8K9W0sWkjWCkpbXJA+37fbOnNqlO1YnQt5ffM/+dPh0="]},"rlimitOps":[{"operation":"OPERATION_UPDATE","state":{"averageBlockNetUsage":{"lastOrdinal":50705255,"valueEx":"434626480","consumed":"443"},"averageBlockCpuUsage":{"lastOrdinal":50705255,"valueEx":"160811581","consumed":"162"},"pendingNetUsage":"2376","pendingCpuUsage":"902","totalNetWeight":"101000000","totalCpuWeight":"101000000","totalRamBytes":"217146944","virtualNetLimit":"1048576","virtualCpuLimit":"400000"}},{"operation":"OPERATION_UPDATE","state":{"averageBlockNetUsage":{"lastOrdinal":50705256,"valueEx":"450804592","consumed":"2808"},"averageBlockCpuUsage":{"lastOrdinal":50705256,"valueEx":"166988151","consumed":"1062"},"totalNetWeight":"101000000","totalCpuWeight":"101000000","totalRamBytes":"217146944","virtualNetLimit":"1048576","virtualCpuLimit":"400000"}}],"filteredTransactions":[{"id":"25774dabfdd1f2d00b53c137f615913b611a24d2dc17fc2dbd53d8cec1950005","index":"4","status":"TRANSACTIONSTATUS_EXECUTED","cpuUsageMicroSeconds":162,"netUsageWords":53,"packedTransaction":{"signatures":["SIG_K1_K1yZT4DFqbdVtPX86hYtA3nAZbeuipx4n9C4XF8Le3djyado45khqtBAWqMBVF4bmKto1BLDYmaHXe3JqTbq3qEtqiiM1U"],"packedTransaction":"SzYyYlSz2aiJxwAAAAABkBfIawLqMFUAAAAAqGzURQGQF8hrAnNz1AAAACBNBZE5nwIBJDE0MTY1YTMzLWVhMjItNGM0OC05NzZiLTU2NzdiOGRkYjcyMgEAAAAAAAAAAAGQF8hrAnNz1AGQF8hrAnNz1AAAAQAAAAAAAAAACFVTRAAAAAABAZAXyGsCc3PU6AMAAAAAAQAAAAAAAcgAAAABAAAAAAEBkBfIawJzc9QAAQF8aHR0cHM6Ly9zMy51cy1lYXN0LTEud2FzYWJpc3lzLmNvbS91bHRyYWlvLXVuaXEtc3RhZ2luZy84ZmE0MTExYzdmZGJlYWUyZDA0NzM0ZWQwY2ZjYmIxYjA0NDViOGViNDhiMjVhNzhjZTQ3MDAyOGQ5M2U4Y2M1LnppcAGPpBEcf9vq4tBHNO0M/LsbBEW460iyWnjORwAo2T6MxQA="}}],"unfilteredTransactionCount":5,"filteredTransactionCount":1,"filteredTransactionTraces":[{"id":"25774dabfdd1f2d00b53c137f615913b611a24d2dc17fc2dbd53d8cec1950005","blockNum":"50705256","index":"5","blockTime":"2022-03-16T19:09:17Z","producerBlockId":"0305b368694e048ae2ca71b22e1ead4af814e807e5d9dbf290f9da73eee8d780","receipt":{"status":"TRANSACTIONSTATUS_EXECUTED","cpuUsageMicroSeconds":162,"netUsageWords":53},"elapsed":"317","netUsage":"424","actionTraces":[{"receiver":"eosio.nft.ft","receipt":{"receiver":"eosio.nft.ft","digest":"42c0e1b6a522b51ac5d401dfbb6993a9b08aa2ec82b3e63152a9b96efc9d1ae1","globalSequence":"133523155","authSequence":[{"accountName":"ultra.nft.ft","sequence":"196"}],"recvSequence":"236","codeSequence":"5","abiSequence":"5"},"action":{"account":"eosio.nft.ft","name":"create","authorization":[{"actor":"ultra.nft.ft","permission":"backend"}],"jsonData":"{\"create\":{\"asset_creator\":\"ultra.nft.ft\",\"asset_manager\":\"ultra.nft.ft\",\"chosen_rate\":null,\"conditionless_receivers\":[\"ultra.nft.ft\"],\"conversion_rate_oracle_contract\":null,\"lockup_time\":0,\"max_mintable_tokens\":200,\"memo\":\"14165a33-ea22-4c48-976b-5677b8ddb722\",\"meta_hash\":\"8fa4111c7fdbeae2d04734ed0cfcbb1b0445b8eb48b25a78ce470028d93e8cc5\",\"meta_uris\":[\"https://s3.us-east-1.wasabisys.com/ultraio-uniq-staging/8fa4111c7fdbeae2d04734ed0cfcbb1b0445b8eb48b25a78ce470028d93e8cc5.zip\"],\"minimum_resell_price\":\"0.00000000 USD\",\"mintable_window_end\":null,\"mintable_window_start\":null,\"recall_window_end\":null,\"recall_window_start\":0,\"resale_shares\":[{\"basis_point\":1000,\"receiver\":\"ultra.nft.ft\"}],\"stat\":null,\"trading_window_end\":null,\"trading_window_start\":null,\"version\":0}}","rawData":"ASQxNDE2NWEzMy1lYTIyLTRjNDgtOTc2Yi01Njc3YjhkZGI3MjIBAAAAAAAAAAABkBfIawJzc9QBkBfIawJzc9QAAAEAAAAAAAAAAAhVU0QAAAAAAQGQF8hrAnNz1OgDAAAAAAEAAAAAAAHIAAAAAQAAAAABAZAXyGsCc3PUAAEBfGh0dHBzOi8vczMudXMtZWFzdC0xLndhc2FiaXN5cy5jb20vdWx0cmFpby11bmlxLXN0YWdpbmcvOGZhNDExMWM3ZmRiZWFlMmQwNDczNGVkMGNmY2JiMWIwNDQ1YjhlYjQ4YjI1YTc4Y2U0NzAwMjhkOTNlOGNjNS56aXABj6QRHH/b6uLQRzTtDPy7GwRFuOtIslp4zkcAKNk+jMU="},"elapsed":"285","transactionId":"25774dabfdd1f2d00b53c137f615913b611a24d2dc17fc2dbd53d8cec1950005","blockNum":"50705256","producerBlockId":"0305b368694e048ae2ca71b22e1ead4af814e807e5d9dbf290f9da73eee8d780","blockTime":"2022-03-16T19:09:17Z","accountRamDeltas":[{"account":"ultra.nft.ft","delta":"368"}],"actionOrdinal":1,"filteringMatched":true}],"dbOps":[{"operation":"OPERATION_UPDATE","code":"eosio.nft.ft","tableName":"next.factory","primaryKey":"next.factory","oldPayer":"eosio.nft.ft","newPayer":"eosio.nft.ft","oldData":"LwAAAAAAAAA=","newData":"MAAAAAAAAAA="},{"operation":"OPERATION_INSERT","code":"eosio.nft.ft","scope":"eosio.nft.ft","tableName":"factory.a","primaryKey":"...........2j","newPayer":"ultra.nft.ft","newData":"LwAAAAAAAACQF8hrAnNz1JAXyGsCc3PUAAAAAAAAAAAAAAAAAAAAAAAIVVNEAAAAAAGQF8hrAnNz1OgDAAAAAAEAAAAAAAEAAAAAAZAXyGsCc3PUAAF8aHR0cHM6Ly9zMy51cy1lYXN0LTEud2FzYWJpc3lzLmNvbS91bHRyYWlvLXVuaXEtc3RhZ2luZy84ZmE0MTExYzdmZGJlYWUyZDA0NzM0ZWQwY2ZjYmIxYjA0NDViOGViNDhiMjVhNzhjZTQ3MDAyOGQ5M2U4Y2M1LnppcI+kERx/2+ri0Ec07Qz8uxsERbjrSLJaeM5HACjZPozFAcgAAAAAAAAAAAAAAA=="}],"ramOps":[{"operation":"OPERATION_PRIMARY_INDEX_ADD","payer":"ultra.nft.ft","delta":"368","usage":"20464","namespace":"NAMESPACE_TABLE_ROW","uniqueKey":"eosio.nft.ft:eosio.nft.ft:factory.a:...........2j","action":"ACTION_ADD"}],"rlimitOps":[{"operation":"OPERATION_UPDATE","accountUsage":{"owner":"ultra.nft.ft","netUsage":{"lastOrdinal":1401545914,"valueEx":"2454","consumed":"424"},"cpuUsage":{"lastOrdinal":1401545914,"valueEx":"938","consumed":"162"},"ramUsage":"20464"}}],"creationTree":[{"creatorActionIndex":-1}]}],"unfilteredTransactionTraceCount":6,"filteredTransactionTraceCount":1,"unfilteredExecutedInputActionCount":6,"filteredExecutedInputActionCount":1,"unfilteredExecutedTotalActionCount":6,"filteredExecutedTotalActionCount":1,"validBlockSigningAuthorityV2":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS5FgTg6xtmMaonVo3uo4S8oLTWTEUnSagAMQCvUkczu9e5hXaWp","weight":1}]}},"activeScheduleV2":{"version":23,"producers":[{"accountName":"eosriobrazil","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS7nq54FcLXtDtY8GHqknYk5NqZPZNBVY3wCJQeRrzVzVjyXsEM6","weight":1}]}}},{"accountName":"cryptolions1","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS8LioNSB3ugUG7LweRBB2BR8AMJUEFGicXMs9msHByd9vMmxEKe","weight":1}]}}},{"accountName":"eosubisoft1","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS5FgTg6xtmMaonVo3uo4S8oLTWTEUnSagAMQCvUkczu9e5hXaWp","weight":1}]}}},{"accountName":"uosswedenorg","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS6j7tqvStvcykc1ysDuNY1J8PqyARpGqWrxbVjiuNBQL8uL7r4A","weight":1}]}}},{"accountName":"eosnationftw","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS5uMMtMvBp4pPEBiUsfdBR9LVnJnzUonGFvQtwnh1eoXzZZKN8U","weight":1}]}}},{"accountName":"ivote4eosusa","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS5gKJLRWxMgLWDsvKu7YaTweoQpqin5SSzJ4mDuaXnQ6tZb1F31","weight":1}]}}},{"accountName":"uoseouldotio","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS7Tauu774Uz1bisTGUC37NsS6mxYoNaW3QKYF3oD1bUUJdCGxKf","weight":1}]}}}]},"filteringApplied":true,"filteringIncludeFilterExpr":"account==\"eosio.nft.ft\" \u0026\u0026 receiver==\"eosio.nft.ft\""} \ No newline at end of file diff --git a/testdata/block-509313.pb.json b/testdata/block-509313.pb.json new file mode 100644 index 0000000..b83364a --- /dev/null +++ b/testdata/block-509313.pb.json @@ -0,0 +1,371 @@ +{ + "id": "0007c581c915a38b9a997d83df1ed1622012dcbb08436cd611b4744cd255a9c4", + "number": 509313, + "version": 1, + "header": { + "timestamp": "2022-04-22T11:19:41Z", + "producer": "produceracc2", + "previous": "0007c580f98bbb0c8c588921ca9a681d7c8a1fcb3b50fe87eb054454fbb3c0fd", + "transactionMroot": "F9qof5QtZhhNlFsVXrGOva0vcQo5xPisJVeconQQuc4=", + "actionMroot": "YMUAICoPM5pLpX6uuwt7RP1rmu9B8mdQvWckDRRypbY=", + "scheduleVersion": 1 + }, + "producerSignature": "SIG_K1_K7AbukviBPgADFzJXmkcbfPuLrDgF4PmG3UQYjRQ5yTYxkQK1kHTAWiMHkUw793qJZ3Bd4nrKV4aPjTzTTFiGJeQs6vpkK", + "dposProposedIrreversibleBlocknum": 509290, + "dposIrreversibleBlocknum": 509266, + "blockrootMerkle": { + "nodeCount": 509312, + "activeNodes": [ + "geLnDlfGNAaf/TAVhRzRrdvbaFo7YzRi4XbXgEYp+Gs=", + "FV8LsjXC0SXMp7d4A2gZpV5xmbO9nw4XWKOQgM0xiEo=", + "SiYNy9CLyyh5TcREvS1yQNMFcIWR/DLx+aT+yzKC52k=", + "Zb0I2/+QFNLggZxGqpF5wq2oFgeR3naLran0H8AezcI=", + "yfAkrUR9PVnyJEmt/gP8OH0cEy/4DHpivVu7VOOpMAo=", + "OmsUwU2mL5w5RRdEfTseOSKjm6yHVHJfMCvHgMflR3U=", + "2yGh2OkC9jVnnm53Nh34dVPuNPGW0Eu8EQ17Fn8tUqM=", + "LcALOsN3c7QX0oOWSrmQE7uMjPt1Uf/iOZQWa96sLmA=", + "JoFlthnIQQo6ZoNOQ4dRmxLa56sRvnyvrfhyuCF9skc=" + ] + }, + "producerToLastProduced": [ + { + "name": "eosio", + "lastBlockNumProduced": 10371 + }, + { + "name": "produceracc1", + "lastBlockNumProduced": 509302 + }, + { + "name": "produceracc2", + "lastBlockNumProduced": 509313 + }, + { + "name": "produceracc3", + "lastBlockNumProduced": 509290 + } + ], + "producerToLastImpliedIrb": [ + { + "name": "produceracc1", + "lastBlockNumProduced": 509278 + }, + { + "name": "produceracc2", + "lastBlockNumProduced": 509290 + }, + { + "name": "produceracc3", + "lastBlockNumProduced": 509266 + } + ], + "confirmCount": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2 + ], + "pendingSchedule": { + "scheduleLibNum": 10370, + "scheduleHash": "JVwLbyk3q9w6Sdx+VNMCDU4BCXbPJFMSn6mkeE9eNAQ=", + "scheduleV2": { + "version": 1 + } + }, + "activatedProtocolFeatures": { + "protocolFeatures": [ + "DsfggBd7LAKyeNUIhhFoa0nXOZJaktm/ys1/xrdAU70=", + "GpmlnYfgbgnsWwKKnLt3SbSlrYgZAENl0C3EN5qLckE=", + "JlL1+WAGKUEJs90LveY2k/VTJK9FK3me4TeoGpBe7SU=", + "KZ3LavaSMkuJmznxbVpTCjMGKATkHwncl+nxVrRHZwc=", + "SpDADVVFTcWwWQVcohNXnG6oVpZ3EqVgF0h4hqTUzA8=", + "TnvzSNoAqUVImypoF0nrVvXeALkAAU4Tfdrjn0j2nWc=", + "T8qL2Cu9GB5xTig/g+G0XZXKWvQPuJrTl3tlPESPeMI=", + "aNyqNMBRfRlmbmszrdZzUdjF9p6ZnKHjeTG8QQopdCg=", + "i6Uv56OVbFzTplajF0uTHTuyq7RVeL78WfKD7NgWpAU=", + "rZ49j2UGh3Cf1o9LkLQffYJaNlsCwjpjbO+IrCrADEM=", + "4PtksQhcxVOJcBWNBaAJwk4nb7lOGgv2pSi0j7xP9SY=", + "70MRLGVDuI2yKDouB3J4wxWuLIRxmosl8lzIhWX76pk=", + "8K9W0sWkjWCkpbXJA+37fbOnNqlO1YnQt5ffM/+dPh0=" + ] + }, + "rlimitOps": [ + { + "operation": "OPERATION_UPDATE", + "state": { + "averageBlockNetUsage": { + "lastOrdinal": 509312, + "valueEx": "151886153", + "consumed": "184" + }, + "averageBlockCpuUsage": { + "lastOrdinal": 509312, + "valueEx": "55713028", + "consumed": "66" + }, + "pendingNetUsage": "2176", + "pendingCpuUsage": "828", + "virtualNetLimit": "1048576", + "virtualCpuLimit": "400000" + } + }, + { + "operation": "OPERATION_UPDATE", + "state": { + "averageBlockNetUsage": { + "lastOrdinal": 509313, + "valueEx": "168753769", + "consumed": "2327" + }, + "averageBlockCpuUsage": { + "lastOrdinal": 509313, + "valueEx": "62148752", + "consumed": "884" + }, + "virtualNetLimit": "1048576", + "virtualCpuLimit": "400000" + } + } + ], + "filteredTransactions": [ + { + "id": "817b9c455cfd7bccb02c7748d8565e96f05a078f1e480a08b460b1e9705b6d66", + "status": "TRANSACTIONSTATUS_EXECUTED", + "cpuUsageMicroSeconds": 818, + "netUsageWords": 268, + "packedTransaction": { + "signatures": [ + "SIG_K1_JzPR5FKAm1CNSgZPUcrswMPUShLCz1DLFaAby4qCuSK42dz2xGQqAMP3VT343eSBZ4jGWfw8eJbgzmNeFo9WioBthXw7qM" + ], + "packedTransaction": "wY9iYnnFOLdP1QAAAAABkBfIawLqMFUAAAAAAKUxdgGQF8hrAnNz1AAAACBNBZE5NgFAYhzGCGOCMQEBRAAAAAAAAAABAAAABG51bGwBGDYyNjI4ZjRhMzhkZTdmMWViMzI2Njc5OQA=" + } + } + ], + "unfilteredTransactionCount": 1, + "filteredTransactionCount": 1, + "filteredTransactionTraces": [ + { + "id": "817b9c455cfd7bccb02c7748d8565e96f05a078f1e480a08b460b1e9705b6d66", + "blockNum": "509313", + "index": "1", + "blockTime": "2022-04-22T11:19:41Z", + "producerBlockId": "0007c581c915a38b9a997d83df1ed1622012dcbb08436cd611b4744cd255a9c4", + "receipt": { + "status": "TRANSACTIONSTATUS_EXECUTED", + "cpuUsageMicroSeconds": 818, + "netUsageWords": 268 + }, + "elapsed": "904", + "netUsage": "2144", + "actionTraces": [ + { + "receiver": "eosio.nft.ft", + "receipt": { + "receiver": "eosio.nft.ft", + "digest": "dd016727b67b13695b80e8494b3d8bfcd49dc1f884f1d4e1e2a5257656eaa71f", + "globalSequence": "605348", + "authSequence": [ + { + "accountName": "ultra.nft.ft", + "sequence": "199" + } + ], + "recvSequence": "5420", + "codeSequence": "3", + "abiSequence": "3" + }, + "action": { + "account": "eosio.nft.ft", + "name": "issue", + "authorization": [ + { + "actor": "ultra.nft.ft", + "permission": "backend" + } + ], + "jsonData": "{\"issue\":{\"memo\":\"62628f4a38de7f1eb3266799\",\"to\":\"aa1aa2aa3ll4\",\"token_configs\":[{\"amount\":1,\"custom_data\":\"null\",\"token_factory_id\":68}]}}", + "rawData": "AUBiHMYIY4IxAQFEAAAAAAAAAAEAAAAEbnVsbAEYNjI2MjhmNGEzOGRlN2YxZWIzMjY2Nzk5" + }, + "elapsed": "756", + "transactionId": "817b9c455cfd7bccb02c7748d8565e96f05a078f1e480a08b460b1e9705b6d66", + "blockNum": "509313", + "producerBlockId": "0007c581c915a38b9a997d83df1ed1622012dcbb08436cd611b4744cd255a9c4", + "blockTime": "2022-04-22T11:19:41Z", + "accountRamDeltas": [ + { + "account": "eosio.nft.ft", + "delta": "136" + }, + { + "account": "ultra.nft.ft", + "delta": "2" + } + ], + "actionOrdinal": 1, + "filteringMatched": true + } + ], + "dbOps": [ + { + "operation": "OPERATION_UPDATE", + "code": "eosio.nft.ft", + "tableName": "next.token", + "primaryKey": "next.token", + "oldPayer": "eosio.nft.ft", + "newPayer": "eosio.nft.ft", + "oldData": "CwAAAAAAAAA=", + "newData": "DAAAAAAAAAA=" + }, + { + "operation": "OPERATION_INSERT", + "code": "eosio.nft.ft", + "scope": "aa1aa2aa3ll4", + "tableName": "token.a", + "primaryKey": "............f", + "newPayer": "eosio.nft.ft", + "newData": "CwAAAAAAAABEAAAAAAAAAE2PYmIEAAAA" + }, + { + "operation": "OPERATION_UPDATE", + "code": "eosio.nft.ft", + "scope": "eosio.nft.ft", + "tableName": "factory.a", + "primaryKey": "...........44", + "oldPayer": "ultra.nft.ft", + "newPayer": "ultra.nft.ft", + "oldData": "RAAAAAAAAACQF8hrAnNz1JAXyGsCc3PUAAAAAAAAAAAAAAAAAAAAAAAIVVNEAAAAAAGQF8hrAnNz1OgDAAAAAAEAAAAAAAEAAAAAAZAXyGsCc3PUAAF4aHR0cHM6Ly9zMy51cy1lYXN0LTEud2FzYWJpc3lzLmNvbS91bHRyYWlvLXVuaXEtZGV2L2RjNzJhYzNhYTkyYzJlMmM0MGJmMjE0YWJkYjllOWE0MTdkYjQ1OWNiZjI2OGQ5NThmMDBmZTVlODZiMjc1NDAuemlw3HKsOqksLixAvyFKvbnppBfbRZy/Jo2VjwD+XoaydUAB6AMAAAMAAAADAAAA", + "newData": "RAAAAAAAAACQF8hrAnNz1JAXyGsCc3PUAAAAAAAAAAAAAAAAAAAAAAAIVVNEAAAAAAGQF8hrAnNz1OgDAAAAAAEAAAAAAAEAAAAAAZAXyGsCc3PUAAF4aHR0cHM6Ly9zMy51cy1lYXN0LTEud2FzYWJpc3lzLmNvbS91bHRyYWlvLXVuaXEtZGV2L2RjNzJhYzNhYTkyYzJlMmM0MGJmMjE0YWJkYjllOWE0MTdkYjQ1OWNiZjI2OGQ5NThmMDBmZTVlODZiMjc1NDAuemlw3HKsOqksLixAvyFKvbnppBfbRZy/Jo2VjwD+XoaydUAB6AMAAAQAAAAEAAAAAAA=" + } + ], + "ramOps": [ + { + "operation": "OPERATION_PRIMARY_INDEX_ADD", + "payer": "eosio.nft.ft", + "delta": "136", + "usage": "1897720", + "namespace": "NAMESPACE_TABLE_ROW", + "uniqueKey": "eosio.nft.ft:aa1aa2aa3ll4:token.a:............f", + "action": "ACTION_ADD" + }, + { + "operation": "OPERATION_PRIMARY_INDEX_UPDATE", + "payer": "ultra.nft.ft", + "delta": "2", + "usage": "64399", + "namespace": "NAMESPACE_TABLE_ROW", + "uniqueKey": "eosio.nft.ft:eosio.nft.ft:factory.a:...........44", + "action": "ACTION_UPDATE" + } + ], + "rlimitOps": [ + { + "operation": "OPERATION_UPDATE", + "accountUsage": { + "owner": "ultra.nft.ft", + "netUsage": { + "lastOrdinal": 1407883162, + "valueEx": "45646", + "consumed": "2145" + }, + "cpuUsage": { + "lastOrdinal": 1407883162, + "valueEx": "17393", + "consumed": "819" + }, + "ramUsage": "64399" + } + } + ], + "creationTree": [ + { + "creatorActionIndex": -1 + } + ] + } + ], + "unfilteredTransactionTraceCount": 2, + "filteredTransactionTraceCount": 1, + "unfilteredExecutedInputActionCount": 2, + "filteredExecutedInputActionCount": 1, + "unfilteredExecutedTotalActionCount": 2, + "filteredExecutedTotalActionCount": 1, + "validBlockSigningAuthorityV2": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", + "weight": 1 + } + ] + } + }, + "activeScheduleV2": { + "version": 1, + "producers": [ + { + "accountName": "produceracc1", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "produceracc2", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", + "weight": 1 + } + ] + } + } + }, + { + "accountName": "produceracc3", + "blockSigningAuthority": { + "v0": { + "threshold": 1, + "keys": [ + { + "publicKey": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", + "weight": 1 + } + ] + } + } + } + ] + }, + "filteringApplied": true, + "filteringIncludeFilterExpr": "executed \u0026\u0026 (action==\"create\" || action==\"update\" || action==\"issue\") \u0026\u0026 account==\"eosio.nft.ft\" \u0026\u0026 receiver==\"eosio.nft.ft\"" +} \ No newline at end of file diff --git a/testdata/block-transactions-6723651.pb.json b/testdata/block-transactions-6723651.pb.json new file mode 100644 index 0000000..e5740c7 --- /dev/null +++ b/testdata/block-transactions-6723651.pb.json @@ -0,0 +1 @@ +{"id":"00669843d4f04cc1b89f6c2bbaebc0254c1d3b39e2d09a81c1fe7eee09629342","number":6723651,"version":1,"header":{"timestamp":"2023-03-03T08:41:29.500Z","producer":"produceracc1","previous":"0066984217fb3ee5d9816cd014d1b4681751a360b6c2731b94fa175853b9b461","transactionMroot":"GC1szFEDAxPr8Vtoe3yme7D/0fMDfGO6IUPNtyNu7hI=","actionMroot":"exbU/x87OvGsmnGlrfVKELjeFLU3P42AznbQj7OWvKE=","scheduleVersion":1},"producerSignature":"SIG_K1_JygRoSK2P1vRvvK2jkYgJF2XQ3LkewNMXoT3hELRTiEWS9RmEojWwcV9FEsM4AZ1QF1jJXhksDgj8Hdtv6512eBm8jFzWA","dposProposedIrreversibleBlocknum":6723627,"dposIrreversibleBlocknum":6723603,"blockrootMerkle":{"nodeCount":6723650,"activeNodes":["9pX4nkWZpy6zizQrNb81UjrRfmzrmc5a18Dbm6SPMZc=","5PnR/jE8t5H3i5rciR2UoYtJOkPCASi3cBeXlXC88SM=","I1Banet0RjACfNfOV1ilJZjRZgRNIB40VUFALJKmFGg=","Cu2M67B8ZbEH3NJmK9BIOCqd/oMsWswGklZ6WCrqizg=","FSoA6kxKa7ED4tV3nhbXrfdld+tJA0/O3SNP62Dokpc=","eAmQMB9YCLh3Dp4I+rG3p35V5aGmpz5wvXcPMTajRVc=","j5RzyPCnn2LEhCB5LeD/pfBV7S6TcFw3eFhD5XxCatU=","nJFJL22RiliHXzeFLDvu7nq1d9VHf9yfqmejxvbEi8A=","IkjMS0R0GpKoDckoexrSmsffXIzn+BWxhVFjxBpnBJ8=","OHBCi+Xvm0fXrF+WJZztU+H7z5LR3GST8PZGZ0ftG2Q="]},"producerToLastProduced":[{"name":"eosio","lastBlockNumProduced":1010},{"name":"produceracc1","lastBlockNumProduced":6723651},{"name":"produceracc2","lastBlockNumProduced":6723627},{"name":"produceracc3","lastBlockNumProduced":6723639}],"producerToLastImpliedIrb":[{"name":"produceracc1","lastBlockNumProduced":6723627},{"name":"produceracc2","lastBlockNumProduced":6723603},{"name":"produceracc3","lastBlockNumProduced":6723615}],"confirmCount":[1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2],"pendingSchedule":{"scheduleLibNum":1009,"scheduleHash":"HqAiKp1fEUk0Ax6ER6ihxb/I/rcx4cjewfezNQ+RMT4=","scheduleV2":{"version":1}},"activatedProtocolFeatures":{"protocolFeatures":["DsfggBd7LAKyeNUIhhFoa0nXOZJaktm/ys1/xrdAU70=","D+GFuIMhUYZYxU8h6R14fymGnFvA3T/HgA95iDwUAw0=","GpmlnYfgbgnsWwKKnLt3SbSlrYgZAENl0C3EN5qLckE=","JlL1+WAGKUEJs90LveY2k/VTJK9FK3me4TeoGpBe7SU=","KZ3LavaSMkuJmznxbVpTCjMGKATkHwncl+nxVrRHZwc=","SpDADVVFTcWwWQVcohNXnG6oVpZ3EqVgF0h4hqTUzA8=","TnvzSNoAqUVImypoF0nrVvXeALkAAU4Tfdrjn0j2nWc=","T8qL2Cu9GB5xTig/g+G0XZXKWvQPuJrTl3tlPESPeMI=","aNyqNMBRfRlmbmszrdZzUdjF9p6ZnKHjeTG8QQopdCg=","i6Uv56OVbFzTplajF0uTHTuyq7RVeL78WfKD7NgWpAU=","rZ49j2UGh3Cf1o9LkLQffYJaNlsCwjpjbO+IrCrADEM=","sLrd2WfnkQuQVpYiShE0yoMsUcth2RpqIiZSqCcVxj8=","4PtksQhcxVOJcBWNBaAJwk4nb7lOGgv2pSi0j7xP9SY=","70MRLGVDuI2yKDouB3J4wxWuLIRxmosl8lzIhWX76pk=","8K9W0sWkjWCkpbXJA+37fbOnNqlO1YnQt5ffM/+dPh0="]},"rlimitOps":[{"operation":"OPERATION_UPDATE","state":{"averageBlockNetUsage":{"lastOrdinal":6723650,"valueEx":"101894243","consumed":"134"},"averageBlockCpuUsage":{"lastOrdinal":6723650,"valueEx":"36637040","consumed":"47"},"pendingNetUsage":"4824","pendingCpuUsage":"1837","totalRamBytes":"4294969344","virtualNetLimit":"1048576","virtualCpuLimit":"400000"}},{"operation":"OPERATION_UPDATE","state":{"averageBlockNetUsage":{"lastOrdinal":6723651,"valueEx":"141245124","consumed":"4926"},"averageBlockCpuUsage":{"lastOrdinal":6723651,"valueEx":"51640065","consumed":"1874"},"totalRamBytes":"4294969344","virtualNetLimit":"1048576","virtualCpuLimit":"400000"}}],"unfilteredTransactions":[{"id":"b96d109b5974db4674597ab508f1461f7e42d52b8b66adcae5ae9e8c018403a9","status":"TRANSACTIONSTATUS_HARDFAIL","cpuUsageMicroSeconds":1827,"netUsageWords":599,"packedTransaction":{"signatures":["SIG_K1_KhvgEHemUhA93aVUvsR1MGU9dWTvhecZurdBzpHAFzF2NgdJ1Vn2iZFjvekZWnkQhECzETBsV1sTRLmWp5wio8UtockqWu"],"packedTransaction":"1rIBZBOYNxOpIgAAAAABAKaCNAPqMFUAAABXLTzNzQEAAAAAAOowVQAAAACo7TIyIQAAAAAA6jBVAAAAAPhED52AlpgAAAAAAAhVT1MAAAAAAAA="}}],"unfilteredTransactionCount":1,"unfilteredImplicitTransactionOps":[{"operation":"OPERATION_CREATE","name":"onblock","transactionId":"f865eb2e6c5f4f06fd0e54d55ebcb1cf38b15be71f2f00538663621193182390","transaction":{"transaction":{"header":{"expiration":"1970-01-01T00:00:00Z"},"actions":[{"account":"eosio","name":"onblock","authorization":[{"actor":"eosio","permission":"active"}],"jsonData":"{\"header\":{\"action_mroot\":\"15f83f66f11726632e763d9a7a693be6d48ef717e779beb49f51f8fff34726fc\",\"confirmed\":0,\"new_producers\":null,\"previous\":\"006698412a6ebc62cf9f92ddeb99e2449c2faf209c7ad509ebe38d57fc363c61\",\"producer\":\"produceracc1\",\"schedule_version\":1,\"timestamp\":1462296178,\"transaction_mroot\":\"0000000000000000000000000000000000000000000000000000000000000000\"}}","rawData":"ct4oVxAQMlchneitAAAAZphBKm68Ys+fkt3rmeJEnC+vIJx61Qnr441X/DY8YQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFfg/ZvEXJmMudj2aemk75tSO9xfneb60n1H4//NHJvwBAAAAAAA="}]}}}],"unfilteredTransactionTraces":[{"id":"f865eb2e6c5f4f06fd0e54d55ebcb1cf38b15be71f2f00538663621193182390","blockNum":"6723651","blockTime":"2023-03-03T08:41:29.500Z","producerBlockId":"00669843d4f04cc1b89f6c2bbaebc0254c1d3b39e2d09a81c1fe7eee09629342","receipt":{"status":"TRANSACTIONSTATUS_EXECUTED","cpuUsageMicroSeconds":10,"netUsageWords":4},"elapsed":"317","netUsage":"32","actionTraces":[{"receiver":"eosio","receipt":{"receiver":"eosio","digest":"1a156dfc36c06de6b8ecaee64ba31f0c9f37084b286369263404e38f01f49104","globalSequence":"6741669","authSequence":[{"accountName":"eosio","sequence":"6723904"}],"recvSequence":"6727733","codeSequence":"3","abiSequence":"3"},"action":{"account":"eosio","name":"onblock","authorization":[{"actor":"eosio","permission":"active"}],"jsonData":"{\"header\":{\"action_mroot\":\"15f83f66f11726632e763d9a7a693be6d48ef717e779beb49f51f8fff34726fc\",\"confirmed\":0,\"new_producers\":null,\"previous\":\"006698412a6ebc62cf9f92ddeb99e2449c2faf209c7ad509ebe38d57fc363c61\",\"producer\":\"produceracc1\",\"schedule_version\":1,\"timestamp\":1462296178,\"transaction_mroot\":\"0000000000000000000000000000000000000000000000000000000000000000\"}}","rawData":"ct4oVxAQMlchneitAAAAZphBKm68Ys+fkt3rmeJEnC+vIJx61Qnr441X/DY8YQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFfg/ZvEXJmMudj2aemk75tSO9xfneb60n1H4//NHJvwBAAAAAAA="},"elapsed":"232","transactionId":"f865eb2e6c5f4f06fd0e54d55ebcb1cf38b15be71f2f00538663621193182390","blockNum":"6723651","producerBlockId":"00669843d4f04cc1b89f6c2bbaebc0254c1d3b39e2d09a81c1fe7eee09629342","blockTime":"2023-03-03T08:41:29.500Z","actionOrdinal":1}],"dbOps":[{"operation":"OPERATION_UPDATE","code":"eosio","scope":"eosio","tableName":"producers","primaryKey":"produceracc1","oldPayer":"produceracc1","newPayer":"produceracc1","oldData":"EBAyVyGd6K0AA+zvgkORECh7xz/6P8iA/NZ9kv7W9vK3CtBtzi+yAEgfAQCAqzgv7PIFAAAAAAEAAAABAAPs74JDkRAoe8c/+j/IgPzWfZL+1vbytwrQbc4vsgBIHwEAgKs4L+zyBQAmMSIA","newData":"EBAyVyGd6K0AA+zvgkORECh7xz/6P8iA/NZ9kv7W9vK3CtBtzi+yAEgfAQCAqzgv7PIFAAAAAAEAAAABAAPs74JDkRAoe8c/+j/IgPzWfZL+1vbytwrQbc4vsgBIHwEAgKs4L+zyBQAnMSIA"},{"operation":"OPERATION_UPDATE","code":"eosio","scope":"eosio","tableName":"global","primaryKey":"global","oldPayer":"eosio","newPayer":"eosio","oldData":"AAAQAAAAAADoAwAAAAAIAAwAAAD0AQAAFAAAAGQAAACAGgYAxAkAAEANAwAKAAAAARAOAABYAgAAgFM7AAAQAAAGAAYAAAAAABAAAAAACAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","newData":"AAAQAAAAAADoAwAAAAAIAAwAAAD0AQAAFAAAAGQAAACAGgYAxAkAAEANAwAKAAAAARAOAABYAgAAgFM7AAAQAAAGAAYAAAAAABAAAAAACAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="},{"operation":"OPERATION_UPDATE","code":"eosio","scope":"eosio","tableName":"chainstate","primaryKey":"chainstate","oldPayer":"eosio","newPayer":"eosio","oldData":"AQAAAAMQEDJXIZ3orSAQMlchneitMBAyVyGd6K1g1vBI7PIFAGB2wNT89QUAAQAAAAOAd/hI7PIFAAA=","newData":"AQAAAAMQEDJXIZ3orSAQMlchneitMBAyVyGd6K1g1vBI7PIFAGB2wNT89QUAAQAAAAOAd/hI7PIFAAA="},{"operation":"OPERATION_UPDATE","code":"eosio","scope":"eosio","tableName":"rammarket","primaryKey":"rammarket","oldPayer":"eosio","newPayer":"eosio","oldData":"AAAAAAgAAAAAUkFNAAAAAAD4//8GAAAAAFJBTQAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAAUkFNAAAAAAAAoAAAAAAAAFJBTQAAAAAA+El2dhIAAAhVT1MAAAAASOF6FK5H4T+amZmZmZmpPwE=","newData":"AAAAAAgAAAAAUkFNAAAAAAD4//8GAAAAAFJBTQAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAAUkFNAAAAAAAAoAAAAAAAAFJBTQAAAAAA+El2dhIAAAhVT1MAAAAASOF6FK5H4T+amZmZmZmpPwE="}],"rlimitOps":[{"operation":"OPERATION_UPDATE","accountUsage":{"owner":"eosio","netUsage":{"lastOrdinal":1462296179,"valueEx":"32693109","consumed":"65"},"cpuUsage":{"lastOrdinal":1462296179,"valueEx":"10127676","consumed":"21"},"ramUsage":"2559094"}}],"creationTree":[{"creatorActionIndex":-1}]},{"id":"b96d109b5974db4674597ab508f1461f7e42d52b8b66adcae5ae9e8c018403a9","blockNum":"6723651","index":"1","blockTime":"2023-03-03T08:41:29.500Z","producerBlockId":"00669843d4f04cc1b89f6c2bbaebc0254c1d3b39e2d09a81c1fe7eee09629342","receipt":{"status":"TRANSACTIONSTATUS_HARDFAIL","cpuUsageMicroSeconds":1827,"netUsageWords":599},"elapsed":"1196","netUsage":"4792","actionTraces":[{"receiver":"eosio.token","action":{"account":"eosio.token","name":"transfer","authorization":[{"actor":"eosio","permission":"active"}],"jsonData":"{\"from\":\"eosio\",\"to\":\"nobody\",\"quantity\":\"0.10000000 UOS\",\"memo\":\"\"}","rawData":"AAAAAADqMFUAAAAA+EQPnYCWmAAAAAAACFVPUwAAAAAA"},"elapsed":"631","transactionId":"b96d109b5974db4674597ab508f1461f7e42d52b8b66adcae5ae9e8c018403a9","blockNum":"6723651","producerBlockId":"00669843d4f04cc1b89f6c2bbaebc0254c1d3b39e2d09a81c1fe7eee09629342","blockTime":"2023-03-03T08:41:29.500Z","exception":{"code":3050003,"name":"eosio_assert_message_exception","message":"eosio_assert_message assertion failure","stack":[{"context":{"level":"error","file":"wasm_interface.cpp","line":1118,"method":"eosio_assert","threadName":"nodeos","timestamp":"2023-03-03T08:41:29.306Z"},"format":"assertion failure with message: ${s}","data":"eyJzIjoidG8gYWNjb3VudCBkb2VzIG5vdCBleGlzdCJ9"},{"context":{"level":"warn","file":"apply_context.cpp","line":117,"method":"exec_one","threadName":"nodeos","timestamp":"2023-03-03T08:41:29.306Z"},"format":"pending console output: ${console}","data":"eyJjb25zb2xlIjoiIn0="}]},"errorCode":"10000000000000000000","actionOrdinal":1}],"exception":{"code":3050003,"name":"eosio_assert_message_exception","message":"eosio_assert_message assertion failure","stack":[{"context":{"level":"error","file":"wasm_interface.cpp","line":1118,"method":"eosio_assert","threadName":"nodeos","timestamp":"2023-03-03T08:41:29.306Z"},"format":"assertion failure with message: ${s}","data":"eyJzIjoidG8gYWNjb3VudCBkb2VzIG5vdCBleGlzdCJ9"},{"context":{"level":"warn","file":"apply_context.cpp","line":117,"method":"exec_one","threadName":"nodeos","timestamp":"2023-03-03T08:41:29.306Z"},"format":"pending console output: ${console}","data":"eyJjb25zb2xlIjoiIn0="}]},"errorCode":"10000000000000000000","rlimitOps":[{"operation":"OPERATION_UPDATE","accountUsage":{"owner":"eosio","netUsage":{"lastOrdinal":1462296179,"valueEx":"32720841","consumed":"4857"},"cpuUsage":{"lastOrdinal":1462296179,"valueEx":"10138249","consumed":"1848"},"ramUsage":"2559094"}}],"creationTree":[{"creatorActionIndex":-1}]}],"unfilteredTransactionTraceCount":2,"unfilteredExecutedInputActionCount":2,"unfilteredExecutedTotalActionCount":2,"validBlockSigningAuthorityV2":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS8dajNwmCPVfr2cNzyWZDwucn5CCPV7nRCYoBB2XAcgCEBWRctX","weight":1}]}},"activeScheduleV2":{"version":1,"producers":[{"accountName":"produceracc1","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS8dajNwmCPVfr2cNzyWZDwucn5CCPV7nRCYoBB2XAcgCEBWRctX","weight":1}]}}},{"accountName":"produceracc2","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS8dajNwmCPVfr2cNzyWZDwucn5CCPV7nRCYoBB2XAcgCEBWRctX","weight":1}]}}},{"accountName":"produceracc3","blockSigningAuthority":{"v0":{"threshold":1,"keys":[{"publicKey":"EOS8dajNwmCPVfr2cNzyWZDwucn5CCPV7nRCYoBB2XAcgCEBWRctX","weight":1}]}}}]}} \ No newline at end of file diff --git a/testdata/empty-fields.abi b/testdata/empty-fields.abi new file mode 100644 index 0000000..e626702 --- /dev/null +++ b/testdata/empty-fields.abi @@ -0,0 +1,23 @@ +{ + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", + "version": "eosio::abi/1.1", + "types": [ + ], + "structs": [ + { + "name": "activers", + "base": "", + "fields": [] + } + ], + "actions": [ + { + "name": "activers", + "type": "activers", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: activate the new nft version\nsummary: 'activate the new nft version to migrate to'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\non-the-fly migration is activated and continuous migration will be allowed. v1 business logic becomes active." + } + ], + "tables": [], + "ricardian_clauses": [], + "variants": [] +} diff --git a/testdata/eosio.abi b/testdata/eosio.abi new file mode 100644 index 0000000..7a136ec --- /dev/null +++ b/testdata/eosio.abi @@ -0,0 +1,2167 @@ +{ + "version": "eosio::abi/1.1", + "types": [ + { + "new_type_name": "block_signing_authority", + "type": "variant_block_signing_authority_v0" + } + ], + "structs": [ + { + "name": "abi_hash", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "hash", + "type": "checksum256" + } + ] + }, + { + "name": "account_tier", + "base": "", + "fields": [ + { + "name": "current_tier", + "type": "name" + }, + { + "name": "is_eligible", + "type": "bool" + } + ] + }, + { + "name": "activate", + "base": "", + "fields": [ + { + "name": "feature_digest", + "type": "checksum256" + } + ] + }, + { + "name": "activatechn", + "base": "" + }, + { + "name": "addbillcode", + "base": "", + "fields": [ + { + "name": "code", + "type": "int32" + }, + { + "name": "type", + "type": "int32" + } + ] + }, + { + "name": "allowpred", + "base": "", + "fields": [ + { + "name": "payer", + "type": "name" + }, + { + "name": "paid_contract", + "type": "name" + }, + { + "name": "paid_action", + "type": "name" + }, + { + "name": "maximum_power_usage", + "type": "uint64" + }, + { + "name": "predicate_contract", + "type": "name?" + }, + { + "name": "predicate_action", + "type": "name?" + } + ] + }, + { + "name": "authority", + "base": "", + "fields": [ + { + "name": "threshold", + "type": "uint32" + }, + { + "name": "keys", + "type": "key_weight[]" + }, + { + "name": "accounts", + "type": "permission_level_weight[]" + }, + { + "name": "waits", + "type": "wait_weight[]" + } + ] + }, + { + "name": "billing_cfg", + "base": "", + "fields": [ + { + "name": "on", + "type": "bool" + }, + { + "name": "hard_failure_codes", + "type": "uint32[]" + }, + { + "name": "soft_failure_codes", + "type": "uint32[]" + }, + { + "name": "strategy", + "type": "uint8" + } + ] + }, + { + "name": "blacklist_ultra", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "level", + "type": "uint32" + } + ] + }, + { + "name": "block_header", + "base": "", + "fields": [ + { + "name": "timestamp", + "type": "uint32" + }, + { + "name": "producer", + "type": "name" + }, + { + "name": "confirmed", + "type": "uint16" + }, + { + "name": "previous", + "type": "checksum256" + }, + { + "name": "transaction_mroot", + "type": "checksum256" + }, + { + "name": "action_mroot", + "type": "checksum256" + }, + { + "name": "schedule_version", + "type": "uint32" + }, + { + "name": "new_producers", + "type": "producer_schedule?" + } + ] + }, + { + "name": "block_signing_authority_v0", + "base": "", + "fields": [ + { + "name": "threshold", + "type": "uint32" + }, + { + "name": "keys", + "type": "key_weight[]" + } + ] + }, + { + "name": "blockchain_parameters", + "base": "", + "fields": [ + { + "name": "max_block_net_usage", + "type": "uint64" + }, + { + "name": "target_block_net_usage_pct", + "type": "uint32" + }, + { + "name": "max_transaction_net_usage", + "type": "uint32" + }, + { + "name": "base_per_transaction_net_usage", + "type": "uint32" + }, + { + "name": "net_usage_leeway", + "type": "uint32" + }, + { + "name": "context_free_discount_net_usage_num", + "type": "uint32" + }, + { + "name": "context_free_discount_net_usage_den", + "type": "uint32" + }, + { + "name": "max_block_cpu_usage", + "type": "uint32" + }, + { + "name": "target_block_cpu_usage_pct", + "type": "uint32" + }, + { + "name": "max_transaction_cpu_usage", + "type": "uint32" + }, + { + "name": "min_transaction_cpu_usage", + "type": "uint32" + }, + { + "name": "ultra_veto_enabled", + "type": "uint8" + }, + { + "name": "max_transaction_lifetime", + "type": "uint32" + }, + { + "name": "deferred_trx_expiration_window", + "type": "uint32" + }, + { + "name": "max_transaction_delay", + "type": "uint32" + }, + { + "name": "max_inline_action_size", + "type": "uint32" + }, + { + "name": "max_inline_action_depth", + "type": "uint16" + }, + { + "name": "max_authority_depth", + "type": "uint16" + } + ] + }, + { + "name": "buy_quote", + "base": "", + "fields": [ + { + "name": "timestamp", + "type": "block_timestamp_type" + }, + { + "name": "cost", + "type": "int64" + }, + { + "name": "fee", + "type": "int64" + }, + { + "name": "bytes", + "type": "int64" + } + ] + }, + { + "name": "buyram", + "base": "", + "fields": [ + { + "name": "payer", + "type": "name" + }, + { + "name": "receiver", + "type": "name" + }, + { + "name": "quant", + "type": "asset" + } + ] + }, + { + "name": "buyrambytes", + "base": "", + "fields": [ + { + "name": "payer", + "type": "name" + }, + { + "name": "receiver", + "type": "name" + }, + { + "name": "bytes", + "type": "uint32" + } + ] + }, + { + "name": "canceldelay", + "base": "", + "fields": [ + { + "name": "canceling_auth", + "type": "permission_level" + }, + { + "name": "trx_id", + "type": "checksum256" + } + ] + }, + { + "name": "chain_state", + "base": "", + "fields": [ + { + "name": "flags", + "type": "uint32" + }, + { + "name": "scheduled_producers", + "type": "name[]" + }, + { + "name": "chain_activated_time", + "type": "time_point" + }, + { + "name": "next_producer_claim_time", + "type": "time_point" + }, + { + "name": "active_schedule_version", + "type": "uint32" + }, + { + "name": "active_schedule_size", + "type": "uint8" + }, + { + "name": "last_schedule_elapsed_update", + "type": "time_point" + }, + { + "name": "schedule_elapsed", + "type": "pair_uint8_uint32[]" + } + ] + }, + { + "name": "contracts_actions_counter", + "base": "", + "fields": [ + { + "name": "count", + "type": "uint64" + } + ] + }, + { + "name": "createtier", + "base": "", + "fields": [ + { + "name": "tier", + "type": "name" + }, + { + "name": "max_free_permission_objects", + "type": "uint64" + }, + { + "name": "max_free_shared_keys", + "type": "uint64" + }, + { + "name": "max_free_permission_levels", + "type": "uint64" + }, + { + "name": "max_free_waits", + "type": "uint64" + }, + { + "name": "max_free_permission_link_objects", + "type": "uint64" + } + ] + }, + { + "name": "default_sponsored_tier", + "base": "", + "fields": [ + { + "name": "tier", + "type": "name" + } + ] + }, + { + "name": "delegatebw", + "base": "", + "fields": [ + { + "name": "from", + "type": "name" + }, + { + "name": "receiver", + "type": "name" + }, + { + "name": "power", + "type": "asset" + }, + { + "name": "transfer", + "type": "bool" + } + ] + }, + { + "name": "delegated_bandwidth", + "base": "", + "fields": [ + { + "name": "from", + "type": "name" + }, + { + "name": "to", + "type": "name" + }, + { + "name": "power_weight", + "type": "asset" + } + ] + }, + { + "name": "deleteauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "permission", + "type": "name" + } + ] + }, + { + "name": "eosio_global_state", + "base": "blockchain_parameters", + "fields": [ + { + "name": "max_ram_size", + "type": "uint64" + }, + { + "name": "total_ram_bytes_reserved", + "type": "uint64" + }, + { + "name": "total_ram_stake", + "type": "int64" + }, + { + "name": "perblock_bucket", + "type": "int64" + }, + { + "name": "new_ram_per_block", + "type": "uint16" + }, + { + "name": "last_ram_increase", + "type": "block_timestamp_type" + } + ] + }, + { + "name": "exchange_state", + "base": "", + "fields": [ + { + "name": "ram_supply", + "type": "asset" + }, + { + "name": "ram_reserved", + "type": "asset" + }, + { + "name": "ram_total_limit_deprecated", + "type": "asset" + }, + { + "name": "ram_unused_limit", + "type": "asset" + }, + { + "name": "ram_purchase_limit", + "type": "asset" + }, + { + "name": "core_reserve", + "type": "asset" + }, + { + "name": "connector_weight", + "type": "float64" + }, + { + "name": "ram_fee_rate", + "type": "float64" + }, + { + "name": "is_trade_enabled", + "type": "bool" + } + ] + }, + { + "name": "free_actions_tier", + "base": "", + "fields": [ + { + "name": "max_free_permission_objects", + "type": "uint64" + }, + { + "name": "max_free_shared_keys", + "type": "uint64" + }, + { + "name": "max_free_permission_levels", + "type": "uint64" + }, + { + "name": "max_free_waits", + "type": "uint64" + }, + { + "name": "max_free_permission_link_objects", + "type": "uint64" + } + ] + }, + { + "name": "giftram", + "base": "", + "fields": [ + { + "name": "receiver", + "type": "name" + }, + { + "name": "bytes", + "type": "int64" + } + ] + }, + { + "name": "init", + "base": "", + "fields": [ + { + "name": "version", + "type": "varuint32" + }, + { + "name": "core", + "type": "symbol" + } + ] + }, + { + "name": "key_weight", + "base": "", + "fields": [ + { + "name": "key", + "type": "public_key" + }, + { + "name": "weight", + "type": "uint16" + } + ] + }, + { + "name": "linkauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "code", + "type": "name" + }, + { + "name": "type", + "type": "name" + }, + { + "name": "requirement", + "type": "name" + } + ] + }, + { + "name": "newaccount", + "base": "", + "fields": [ + { + "name": "creator", + "type": "name" + }, + { + "name": "name", + "type": "name" + }, + { + "name": "owner", + "type": "authority" + }, + { + "name": "active", + "type": "authority" + } + ] + }, + { + "name": "newactconfig", + "base": "", + "fields": [ + { + "name": "cost", + "type": "asset" + }, + { + "name": "oracle", + "type": "name" + }, + { + "name": "candidate_moving_average", + "type": "asset[]" + } + ] + }, + { + "name": "newebact", + "base": "", + "fields": [ + { + "name": "active", + "type": "authority" + }, + { + "name": "major_idp", + "type": "name" + }, + { + "name": "major_idp_id", + "type": "string" + }, + { + "name": "account", + "type": "name?" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "newnonebact", + "base": "", + "fields": [ + { + "name": "creator", + "type": "name" + }, + { + "name": "owner", + "type": "authority" + }, + { + "name": "active", + "type": "authority" + }, + { + "name": "max_payment", + "type": "asset" + } + ] + }, + { + "name": "next_eba_num", + "base": "", + "fields": [ + { + "name": "num", + "type": "uint64" + } + ] + }, + { + "name": "next_non_eba_num", + "base": "", + "fields": [ + { + "name": "num", + "type": "uint64" + } + ] + }, + { + "name": "non_eba_config", + "base": "", + "fields": [ + { + "name": "cost", + "type": "asset" + }, + { + "name": "oracle", + "type": "name" + }, + { + "name": "ma_settings", + "type": "asset[]" + } + ] + }, + { + "name": "onblock", + "base": "", + "fields": [ + { + "name": "header", + "type": "block_header" + } + ] + }, + { + "name": "onerror", + "base": "", + "fields": [ + { + "name": "sender_id", + "type": "uint128" + }, + { + "name": "sent_trx", + "type": "bytes" + } + ] + }, + { + "name": "pair_uint8_uint32", + "base": "", + "fields": [ + { + "name": "key", + "type": "uint8" + }, + { + "name": "value", + "type": "uint32" + } + ] + }, + { + "name": "payer_contract_action_id", + "base": "", + "fields": [ + { + "name": "paid_action", + "type": "name" + }, + { + "name": "paid_contract_action_id", + "type": "uint64" + } + ] + }, + { + "name": "payer_predicate", + "base": "", + "fields": [ + { + "name": "paid_contract_action_id", + "type": "uint64" + }, + { + "name": "maximum_power_usage", + "type": "uint64" + }, + { + "name": "predicate_contract", + "type": "name?" + }, + { + "name": "predicate_action", + "type": "name?" + } + ] + }, + { + "name": "permission_level", + "base": "", + "fields": [ + { + "name": "actor", + "type": "name" + }, + { + "name": "permission", + "type": "name" + } + ] + }, + { + "name": "permission_level_weight", + "base": "", + "fields": [ + { + "name": "permission", + "type": "permission_level" + }, + { + "name": "weight", + "type": "uint16" + } + ] + }, + { + "name": "producer_info", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "producer_key", + "type": "public_key" + }, + { + "name": "is_active", + "type": "bool" + }, + { + "name": "url", + "type": "string" + }, + { + "name": "last_claim_time", + "type": "time_point" + }, + { + "name": "location", + "type": "uint16" + }, + { + "name": "producer_authority", + "type": "block_signing_authority$" + }, + { + "name": "activate_time", + "type": "time_point" + }, + { + "name": "unclaimed_blocks", + "type": "uint32" + } + ] + }, + { + "name": "producer_key", + "base": "", + "fields": [ + { + "name": "producer_name", + "type": "name" + }, + { + "name": "block_signing_key", + "type": "public_key" + } + ] + }, + { + "name": "producer_schedule", + "base": "", + "fields": [ + { + "name": "version", + "type": "uint32" + }, + { + "name": "producers", + "type": "producer_key[]" + } + ] + }, + { + "name": "ram_offer", + "base": "", + "fields": [ + { + "name": "total_ram_gifted", + "type": "int64" + } + ] + }, + { + "name": "ram_payment", + "base": "", + "fields": [ + { + "name": "total_uos", + "type": "int64" + } + ] + }, + { + "name": "ram_purchase", + "base": "", + "fields": [ + { + "name": "total_ram_bought", + "type": "int64" + } + ] + }, + { + "name": "ram_sponsor_account", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + } + ] + }, + { + "name": "ram_whitelist", + "base": "", + "fields": [ + { + "name": "ram_unused_limit", + "type": "uint64" + } + ] + }, + { + "name": "reclaimram", + "base": "", + "fields": [ + { + "name": "receiver", + "type": "name" + }, + { + "name": "max_bytes", + "type": "int64" + } + ] + }, + { + "name": "refund", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + } + ] + }, + { + "name": "refund_request", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "request_time", + "type": "time_point_sec" + }, + { + "name": "power_amount", + "type": "asset" + } + ] + }, + { + "name": "refundram", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "bytes", + "type": "int64" + } + ] + }, + { + "name": "regproducer", + "base": "", + "fields": [ + { + "name": "producer", + "type": "name" + }, + { + "name": "producer_key", + "type": "public_key" + }, + { + "name": "url", + "type": "string" + }, + { + "name": "location", + "type": "uint16" + } + ] + }, + { + "name": "regproducer2", + "base": "", + "fields": [ + { + "name": "producer", + "type": "name" + }, + { + "name": "producer_authority", + "type": "block_signing_authority" + }, + { + "name": "url", + "type": "string" + }, + { + "name": "location", + "type": "uint16" + } + ] + }, + { + "name": "restriction_level_configs", + "base": "", + "fields": [ + { + "name": "max_voting_duration_sec", + "type": "uint32" + } + ] + }, + { + "name": "restriction_level_vote", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "votes", + "type": "name[]" + }, + { + "name": "block_time", + "type": "block_timestamp_type" + } + ] + }, + { + "name": "resvrambytes", + "base": "", + "fields": [ + { + "name": "bytes", + "type": "int64" + } + ] + }, + { + "name": "revokepred", + "base": "", + "fields": [ + { + "name": "payer", + "type": "name" + }, + { + "name": "paid_contract", + "type": "name" + }, + { + "name": "paid_action", + "type": "name" + } + ] + }, + { + "name": "rmbillcode", + "base": "", + "fields": [ + { + "name": "code", + "type": "int32" + }, + { + "name": "type", + "type": "int32" + } + ] + }, + { + "name": "rtnrambytes", + "base": "", + "fields": [ + { + "name": "bytes", + "type": "int64" + } + ] + }, + { + "name": "setabi", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "abi", + "type": "bytes" + } + ] + }, + { + "name": "setacblcklst", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "level", + "type": "uint32" + } + ] + }, + { + "name": "setacctpow", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "power_weight", + "type": "int64?" + } + ] + }, + { + "name": "setacctram", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "ram_bytes", + "type": "int64?" + } + ] + }, + { + "name": "setalimits", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "ram_bytes", + "type": "int64" + }, + { + "name": "power_weight", + "type": "int64" + } + ] + }, + { + "name": "setbilling", + "base": "", + "fields": [ + { + "name": "on", + "type": "bool" + } + ] + }, + { + "name": "setbillstrat", + "base": "", + "fields": [ + { + "name": "s", + "type": "int32" + } + ] + }, + { + "name": "setcode", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "vmtype", + "type": "uint8" + }, + { + "name": "vmversion", + "type": "uint8" + }, + { + "name": "code", + "type": "bytes" + } + ] + }, + { + "name": "setdeftier", + "base": "", + "fields": [ + { + "name": "tier", + "type": "name" + } + ] + }, + { + "name": "setparams", + "base": "", + "fields": [ + { + "name": "params", + "type": "blockchain_parameters" + } + ] + }, + { + "name": "setpriv", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "is_priv", + "type": "uint8" + } + ] + }, + { + "name": "setprods", + "base": "", + "fields": [ + { + "name": "schedule", + "type": "name[]" + } + ] + }, + { + "name": "setram", + "base": "", + "fields": [ + { + "name": "max_ram_size", + "type": "uint64" + } + ] + }, + { + "name": "setramcurve", + "base": "", + "fields": [ + { + "name": "core_reserve", + "type": "asset" + }, + { + "name": "ram_supply", + "type": "int64" + }, + { + "name": "connector_weight", + "type": "float64" + } + ] + }, + { + "name": "setramfee", + "base": "", + "fields": [ + { + "name": "ram_fee_percent", + "type": "float64" + } + ] + }, + { + "name": "setramprchlm", + "base": "", + "fields": [ + { + "name": "ram_purchase_limit", + "type": "uint64" + } + ] + }, + { + "name": "setramrate", + "base": "", + "fields": [ + { + "name": "bytes_per_block", + "type": "uint16" + } + ] + }, + { + "name": "setramsponsr", + "base": "", + "fields": [ + { + "name": "ram_sponsor", + "type": "name" + } + ] + }, + { + "name": "setramtrade", + "base": "", + "fields": [ + { + "name": "state", + "type": "bool" + } + ] + }, + { + "name": "setrankalgo", + "base": "", + "fields": [ + { + "name": "algorithm", + "type": "uint32" + } + ] + }, + { + "name": "settax", + "base": "", + "fields": [ + { + "name": "country_subregion", + "type": "symbol_code" + }, + { + "name": "percentage", + "type": "uint64" + } + ] + }, + { + "name": "settier", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "tier", + "type": "name" + } + ] + }, + { + "name": "settiereligb", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "is_eligible", + "type": "bool" + } + ] + }, + { + "name": "setunusedlm", + "base": "", + "fields": [ + { + "name": "ram_unused_limit", + "type": "uint64" + } + ] + }, + { + "name": "taxes", + "base": "", + "fields": [ + { + "name": "percentage", + "type": "uint64" + } + ] + }, + { + "name": "unclaimed_block", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "blocks", + "type": "uint32" + } + ] + }, + { + "name": "undelegatebw", + "base": "", + "fields": [ + { + "name": "from", + "type": "name" + }, + { + "name": "receiver", + "type": "name" + }, + { + "name": "power", + "type": "asset" + } + ] + }, + { + "name": "unlinkauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "code", + "type": "name" + }, + { + "name": "type", + "type": "name" + } + ] + }, + { + "name": "unregprod", + "base": "", + "fields": [ + { + "name": "producer", + "type": "name" + } + ] + }, + { + "name": "unwhtlistact", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + } + ] + }, + { + "name": "updateauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "permission", + "type": "name" + }, + { + "name": "parent", + "type": "name" + }, + { + "name": "auth", + "type": "authority" + } + ] + }, + { + "name": "updatetier", + "base": "", + "fields": [ + { + "name": "tier", + "type": "name" + }, + { + "name": "max_free_permission_objects", + "type": "uint64" + }, + { + "name": "max_free_shared_keys", + "type": "uint64" + }, + { + "name": "max_free_permission_levels", + "type": "uint64" + }, + { + "name": "max_free_waits", + "type": "uint64" + }, + { + "name": "max_free_permission_link_objects", + "type": "uint64" + } + ] + }, + { + "name": "user_resources", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "power_weight", + "type": "asset" + }, + { + "name": "ram_bytes", + "type": "int64" + }, + { + "name": "flags", + "type": "uint32" + } + ] + }, + { + "name": "voteban", + "base": "", + "fields": [ + { + "name": "voter", + "type": "name" + }, + { + "name": "account", + "type": "name" + } + ] + }, + { + "name": "votebancfg", + "base": "", + "fields": [ + { + "name": "max_voting_duration_sec", + "type": "uint32" + } + ] + }, + { + "name": "wait_weight", + "base": "", + "fields": [ + { + "name": "wait_sec", + "type": "uint32" + }, + { + "name": "weight", + "type": "uint16" + } + ] + }, + { + "name": "whitelistact", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "ram_unused_limit", + "type": "uint64" + } + ] + } + ], + "actions": [ + { + "name": "activate", + "type": "activate", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Activate Protocol Feature\nsummary: 'Activate protocol feature {{nowrap feature_digest}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\n{{$action.account}} activates the protocol feature with a digest of {{feature_digest}}." + }, + { + "name": "activatechn", + "type": "activatechn", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Activate the Chain\nsummary: 'Activates the chain.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nActivates the chain for multi-producer mode." + }, + { + "name": "addbillcode", + "type": "addbillcode", + "ricardian_contract": "" + }, + { + "name": "allowpred", + "type": "allowpred", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Allow predicate action\nsummary: 'Adds a payer predicate for specific contract and action'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\n{{payer}}} adds a predicate for {{paid_contract}} {{paid_action}} to allow other accounts to add him as a @payer on this contract-action pair." + }, + { + "name": "buyram", + "type": "buyram", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Buy RAM\nsummary: '{{nowrap payer}} buys RAM on behalf of {{nowrap receiver}} by paying {{nowrap quant}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/resource.png#3830f1ce8cb07f7757dbcf383b1ec1b11914ac34a1f9d8b065f07600fa9dac19\n---\n\n{{payer}} buys RAM on behalf of {{receiver}} by paying {{quant}}. This transaction will incur a 0.5% fee out of {{quant}} and the amount of RAM delivered will depend on market rates." + }, + { + "name": "buyrambytes", + "type": "buyrambytes", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Buy RAM\nsummary: '{{nowrap payer}} buys {{nowrap bytes}} bytes of RAM on behalf of {{nowrap receiver}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/resource.png#3830f1ce8cb07f7757dbcf383b1ec1b11914ac34a1f9d8b065f07600fa9dac19\n---\n\n{{payer}} buys approximately {{bytes}} bytes of RAM on behalf of {{receiver}} by paying market rates for RAM. This transaction will incur a 0.5% fee and the cost will depend on market rates." + }, + { + "name": "canceldelay", + "type": "canceldelay", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Cancel Delayed Transaction\nsummary: '{{nowrap canceling_auth.actor}} cancels a delayed transaction'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{canceling_auth.actor}} cancels the delayed transaction with id {{trx_id}}." + }, + { + "name": "createtier", + "type": "createtier", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create a new sponsored actions tier\nsummary: 'Create a new sponsored actions tier with specified limitations'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nNew tier {{tier}} is created with provided maximum item storage limitations." + }, + { + "name": "delegatebw", + "type": "delegatebw", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Stake Tokens for POWER\nsummary: 'Stake tokens for POWER and optionally transfer ownership'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/resource.png#3830f1ce8cb07f7757dbcf383b1ec1b11914ac34a1f9d8b065f07600fa9dac19\n---\n\n{{#if transfer}} {{from}} stakes POWER on behalf of {{receiver}}.\n\nStaked tokens will also be transferred to {{receiver}}. The sum of these two quantities will be deducted from {{from}}’s liquid balance and add to the vote weight of {{receiver}}.\n{{else}}\n{{from}} stakes POWER to self and delegates to {{receiver}}.\n\nThe sum of these two quantities add to the vote weight of {{from}}.\n{{/if}}" + }, + { + "name": "deleteauth", + "type": "deleteauth", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Delete Account Permission\nsummary: 'Delete the {{nowrap permission}} permission of {{nowrap account}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nDelete the {{permission}} permission of {{account}}." + }, + { + "name": "giftram", + "type": "giftram", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Gift RAM\nsummary: 'Gift ram bytes to a user.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{reciever}} will receive {{bytes}} bytes from the ram supply." + }, + { + "name": "init", + "type": "init", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Initialize System Contract\nsummary: 'Initialize system contract'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\nInitialize system contract. The core token symbol will be set to {{core}}." + }, + { + "name": "linkauth", + "type": "linkauth", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Link Action to Permission\nsummary: '{{nowrap account}} sets the minimum required permission for the {{#if type}}{{nowrap type}} action of the{{/if}} {{nowrap code}} contract to {{nowrap requirement}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{account}} sets the minimum required permission for the {{#if type}}{{type}} action of the{{/if}} {{code}} contract to {{requirement}}.\n\n{{#if type}}{{else}}Any links explicitly associated to specific actions of {{code}} will take precedence.{{/if}}" + }, + { + "name": "newaccount", + "type": "newaccount", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create New Account\nsummary: '{{nowrap creator}} creates a new account with the name {{nowrap name}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{creator}} creates a new account with the name {{name}} and the following permissions:\n\nowner permission with authority:\n{{to_json owner}}\n\nactive permission with authority:\n{{to_json active}}" + }, + { + "name": "newactconfig", + "type": "newactconfig", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set non eba config\nsummary: 'Set non eba account creation policy'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{cost}} will be set for non eba account creation and which {{oracle}} contract and {{candicate_moving_average}} the rate will base on." + }, + { + "name": "newebact", + "type": "newebact", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create EBA account\nsummary: 'Create new EBA account'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nNew account with generated name will be created with {{active}} authority and {{major_idp}} with {{major_ipd_id}} as account default id providers." + }, + { + "name": "newnonebact", + "type": "newnonebact", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create non-EBA account\nsummary: 'Create new non-EBA account'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{creator}} will be able to create new account with {{owner}} authority and {{active}} authority with cost that should not exceed {{max_payment}}." + }, + { + "name": "onblock", + "type": "onblock", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: onblock\nsummary: 'Currently not useable.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nNo description is provided." + }, + { + "name": "onerror", + "type": "onerror", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: When an error occurs\nsummary: 'Currently not useable.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nNo description is provided." + }, + { + "name": "reclaimram", + "type": "reclaimram", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Reclaim RAM\nsummary: 'Reclaim max gifted ram bytes from a user.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{reciever}} will try to return {{max_bytes}} gifted bytes to the ram supply." + }, + { + "name": "refund", + "type": "refund", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Claim Unstaked Tokens\nsummary: 'Return previously unstaked tokens to {{nowrap owner}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nReturn previously unstaked tokens to {{owner}} after the unstaking period has elapsed." + }, + { + "name": "refundram", + "type": "refundram", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Refund RAM From Account\nsummary: 'Refund unused RAM from {{nowrap account}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/resource.png#3830f1ce8cb07f7757dbcf383b1ec1b11914ac34a1f9d8b065f07600fa9dac19\n---\n\nRefund {{bytes}} bytes of unused RAM from account {{account}} at the price which is averaged based on total UOS spent for RAM purchase and actual RAM that was purchased in this way. This action will weigh the gifted RAM and bought RAM depending on their amount." + }, + { + "name": "regproducer", + "type": "regproducer", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Register as a Block Producer Candidate\nsummary: 'Register {{nowrap producer}} account as a block producer candidate'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/voting.png#db28cd3db6e62d4509af3644ce7d377329482a14bb4bfaca2aa5f1400d8e8a84\n---\n\nRegister {{producer}} account as a block producer candidate.\n\nURL: {{url}}\nLocation code: {{location}}\nBlock signing key: {{producer_key}}\n\n## Block Producer Agreement\n{{$clauses.BlockProducerAgreement}}" + }, + { + "name": "regproducer2", + "type": "regproducer2", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Register as a Block Producer Candidate\nsummary: 'Register {{nowrap producer}} account as a block producer candidate'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/voting.png#db28cd3db6e62d4509af3644ce7d377329482a14bb4bfaca2aa5f1400d8e8a84\n---\n\nRegister {{producer}} account as a block producer candidate.\n\nURL: {{url}}\nLocation code: {{location}}\nBlock signing authority:\n{{to_json producer_authority}}\n\n## Block Producer Agreement\n{{$clauses.BlockProducerAgreement}}" + }, + { + "name": "resvrambytes", + "type": "resvrambytes", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Reserve Ram Bytes\nsummary: 'Reserve ram from ram supply.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{bytes}} bytes will be reserved from the total ram supply." + }, + { + "name": "revokepred", + "type": "revokepred", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Revoke predicate action\nsummary: 'Removes a payer predicate for specific contract and action'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\n{{payer}}} removes a predicate for {{paid_contract}} {{paid_action}} to no longer allow other accounts to add him as a @payer on this contract-action pair." + }, + { + "name": "rmbillcode", + "type": "rmbillcode", + "ricardian_contract": "" + }, + { + "name": "rtnrambytes", + "type": "rtnrambytes", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Return RAM Bytes\nsummary: 'Return reserved ram to ram supply.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{bytes}} bytes will be returned back to the total ram supply." + }, + { + "name": "setabi", + "type": "setabi", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Deploy Contract ABI\nsummary: 'Deploy contract ABI on account {{nowrap account}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nDeploy the ABI file associated with the contract on account {{account}}." + }, + { + "name": "setacblcklst", + "type": "setacblcklst", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set account blacklist status\nsummary: 'Set account level in ultra blacklist limiting transaction execution'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{account}} will be assigned status {{level}} in ultra blacklist thus altering the limits of transaction execution for it." + }, + { + "name": "setacctpow", + "type": "setacctpow", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Explicitly Manage the POWER Quota of Account\nsummary: 'Explicitly manage the POWER bandwidth quota of account {{nowrap account}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\n{{#if_has_value power_weight}}\nExplicitly manage the POWER quota of account {{account}} by pinning it to a weight of {{power_weight}}.\n\n{{account}} can stake and unstake, however, it will not change their POWER quota as long as it remains pinned.\n{{else}}\nUnpin the POWER quota of account {{account}}. The POWER quota of {{account}} will be driven by the current tokens staked for POWER by {{account}}.\n{{/if_has_value}}" + }, + { + "name": "setacctram", + "type": "setacctram", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Explicitly Manage the RAM Quota of Account\nsummary: 'Explicitly manage the RAM quota of account {{nowrap account}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\n{{#if_has_value ram_bytes}}\nExplicitly manage the RAM quota of account {{account}} by pinning it to {{ram_bytes}} bytes.\n\n{{account}} can buy and sell RAM, however, it will not change their RAM quota as long as it remains pinned.\n{{else}}\nUnpin the RAM quota of account {{account}}. The RAM quota of {{account}} will be driven by the current RAM holdings of {{account}}.\n{{/if_has_value}}" + }, + { + "name": "setalimits", + "type": "setalimits", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Adjust Resource Limits of Account\nsummary: 'Adjust resource limits of account {{nowrap account}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\n{{$action.account}} updates {{account}}’s resource limits to have a RAM quota of {{ram_bytes}} bytes, and a POWER quota of {{power_weight}}." + }, + { + "name": "setbilling", + "type": "setbilling", + "ricardian_contract": "" + }, + { + "name": "setbillstrat", + "type": "setbillstrat", + "ricardian_contract": "" + }, + { + "name": "setcode", + "type": "setcode", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Deploy Contract Code\nsummary: 'Deploy contract code on account {{nowrap account}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nDeploy compiled contract code to the account {{account}}." + }, + { + "name": "setdeftier", + "type": "setdeftier", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set default sponsored actions tier for new account\nsummary: 'Overwrites default sponsored tier for all new accounts'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nDefault sponsored actions tier for all new accounts is replaced with {{tier}}." + }, + { + "name": "setparams", + "type": "setparams", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set System Parameters\nsummary: 'Set System Parameters'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\n{{$action.account}} sets system parameters to:\n{{to_json params}}" + }, + { + "name": "setpriv", + "type": "setpriv", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Make an Account Privileged or Unprivileged\nsummary: '{{#if is_priv}}Make {{nowrap account}} privileged{{else}}Remove privileged status of {{nowrap account}}{{/if}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\n{{#if is_priv}}\n{{$action.account}} makes {{account}} privileged.\n{{else}}\n{{$action.account}} removes privileged status of {{account}}.\n{{/if}}" + }, + { + "name": "setprods", + "type": "setprods", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set Producing Schedule\nsummary: 'Set new list of producing producer.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{schedule}} will be set as new list of producing producer or a new producer schedule" + }, + { + "name": "setram", + "type": "setram", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Configure the Available RAM\nsummary: 'Configure the available RAM'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\n{{$action.account}} configures the available RAM to {{max_ram_size}} bytes." + }, + { + "name": "setramcurve", + "type": "setramcurve", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set the Ram Market Curve\nsummary: 'Set the state of ram market and selling curve by overwriting current settings'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{core_reserve}}, {{ram_supply}} and {{connector_weight}} will be used to initialize RAM market by overwriting current values for market settings." + }, + { + "name": "setramfee", + "type": "setramfee", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set the Ram Purchase Fee\nsummary: 'Set the ram fee percentage applied to ram purchases'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nOverwrite current ram purchase fee rate to {{ram_fee_percent}}% that is applied to each purchase." + }, + { + "name": "setramprchlm", + "type": "setramprchlm", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set the RAM Purchase Limit in a Single Buy Action\nsummary: 'Set the RAM purchase limit for all users in a single buy action'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{ram_purchase_limit}} is used to overwrite current ram purchase limit for all accounts limiting the maximum ram purchased in a single buy action." + }, + { + "name": "setramrate", + "type": "setramrate", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set the Rate of Increase of RAM\nsummary: 'Set the rate of increase of RAM per block'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\n{{$action.account}} sets the rate of increase of RAM to {{bytes_per_block}} bytes/block." + }, + { + "name": "setramsponsr", + "type": "setramsponsr", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set tiered system ram sponsor account\nsummary: 'Initializes account that will sponsor the tiered system'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nAccount used for RAM sponsorship in the tiered system will be initialzied and set to {{ram_sponsor}}." + }, + { + "name": "setramtrade", + "type": "setramtrade", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set RAM trading\nsummary: 'Enable or disable RAM trading'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nRAM trading system state will be set to {{state}}" + }, + { + "name": "setrankalgo", + "type": "setrankalgo", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Change the transaction queue's ranking algorithm\nsummary: 'Changes the way that queued transactions are pulled out of the queue and pushed into blocks'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\nChange the ranking algorithm for queued transactions to flag type {{algorithm}}" + }, + { + "name": "settax", + "type": "settax", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set Tax Rate\nsummary: 'Set tax rate for a Country'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{country_code}} country and sub region {{sub_region}} will be taxed at a {{percentage}} percentage for sales." + }, + { + "name": "settier", + "type": "settier", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set accounts sponsored actions tier\nsummary: 'Overwrites current user sponsored actions tier with a new tier'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nCurrent tier of {{account}} is overwritten with a new tier {{tier}}." + }, + { + "name": "settiereligb", + "type": "settiereligb", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set account eligibility for sponsored actions system\nsummary: 'Enables or disables sponsored actions system of account'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nEligibility for sponsored actions system of account {{account}} is overwritten to be {{is_eligible}}" + }, + { + "name": "setunusedlm", + "type": "setunusedlm", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set the Unused Ram limit\nsummary: 'Set the unused ram limit'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nany account cannot own more unused ram than {{ram_unused_limit}}." + }, + { + "name": "undelegatebw", + "type": "undelegatebw", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Unstake Tokens for POWER\nsummary: 'Unstake tokens for POWER from {{nowrap receiver}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/resource.png#3830f1ce8cb07f7757dbcf383b1ec1b11914ac34a1f9d8b065f07600fa9dac19\n---\n\n{{from}} unstakes power from {{receiver}}.\n\nThe sum of these two quantities will be removed from the vote weight of {{receiver}} and will be made available to {{from}} after an uninterrupted 3 day period without further unstaking by {{from}}. After the uninterrupted 3 day period passes, the system will attempt to automatically return the funds to {{from}}’s regular token balance. However, this automatic refund may occasionally fail which will then require {{from}} to manually claim the funds with the refund action." + }, + { + "name": "unlinkauth", + "type": "unlinkauth", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Unlink Action from Permission\nsummary: '{{nowrap account}} unsets the minimum required permission for the {{#if type}}{{nowrap type}} action of the{{/if}} {{nowrap code}} contract'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{account}} removes the association between the {{#if type}}{{type}} action of the{{/if}} {{code}} contract and its minimum required permission.\n\n{{#if type}}{{else}}This will not remove any links explicitly associated to specific actions of {{code}}.{{/if}}" + }, + { + "name": "unregprod", + "type": "unregprod", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Unregister as a Block Producer Candidate\nsummary: '{{nowrap producer}} unregisters as a block producer candidate'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/voting.png#db28cd3db6e62d4509af3644ce7d377329482a14bb4bfaca2aa5f1400d8e8a84\n---\n\n{{producer}} unregisters as a block producer candidate. {{producer}} account will retain its votes and those votes can change based on voter stake changes or votes removed from {{producer}}. However new voters will not be able to vote for {{producer}} while it remains unregistered." + }, + { + "name": "unwhtlistact", + "type": "unwhtlistact", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Remove a User from the Whitelist in RAM Market\nsummary: 'Remove a user from the whitelist'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{account}} is removed from the whitelist and respective KYC limits now apply limiting the ram purchases." + }, + { + "name": "updateauth", + "type": "updateauth", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Modify Account Permission\nsummary: 'Add or update the {{nowrap permission}} permission of {{nowrap account}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nModify, and create if necessary, the {{permission}} permission of {{account}} to have a parent permission of {{parent}} and the following authority:\n{{to_json auth}}" + }, + { + "name": "updatetier", + "type": "updatetier", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Modify existing sponsored actions tier\nsummary: 'Updates an existing sponsored actions tier with specified new limitations'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nExisting tier {{tier}} is updated and current item storage limitations are overwritten with a new ones." + }, + { + "name": "voteban", + "type": "voteban", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Vote for account ban\nsummary: 'Vote to raise account's ban level'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nVoter {{voter}} will add a vote for raising account {{account}}'s ban level to the next higher restrictive level. When enough votes are accumulated changes will take place chain-wide" + }, + { + "name": "votebancfg", + "type": "votebancfg", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set ban voting settings\nsummary: 'Set ban voting settings'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nBan voting duration by voters will be set to {{max_voting_duration_sec}}" + }, + { + "name": "whitelistact", + "type": "whitelistact", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Add a User to the Whitelist in RAM Market\nsummary: 'Add a user to the whitelist and set a custom threshold for his unused ram'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{account}} is added to the whitelist removing a total ram limit from it and allowing to have an unused ram threshold of {{ram_unused_limit}}." + } + ], + "tables": [ + { + "name": "abihash", + "index_type": "i64", + "type": "abi_hash" + }, + { + "name": "accounttier", + "index_type": "i64", + "type": "account_tier" + }, + { + "name": "billingcfg", + "index_type": "i64", + "type": "billing_cfg" + }, + { + "name": "blcklstultra", + "index_type": "i64", + "type": "blacklist_ultra" + }, + { + "name": "buyquote", + "index_type": "i64", + "type": "buy_quote" + }, + { + "name": "chainstate", + "index_type": "i64", + "type": "chain_state" + }, + { + "name": "contactcntr", + "index_type": "i64", + "type": "contracts_actions_counter" + }, + { + "name": "defaulttier", + "index_type": "i64", + "type": "default_sponsored_tier" + }, + { + "name": "delband", + "index_type": "i64", + "type": "delegated_bandwidth" + }, + { + "name": "freeacttier", + "index_type": "i64", + "type": "free_actions_tier" + }, + { + "name": "global", + "index_type": "i64", + "type": "eosio_global_state" + }, + { + "name": "next.eba", + "index_type": "i64", + "type": "next_eba_num" + }, + { + "name": "next.noneba", + "index_type": "i64", + "type": "next_non_eba_num" + }, + { + "name": "nonebaconfig", + "index_type": "i64", + "type": "non_eba_config" + }, + { + "name": "paycntactid", + "index_type": "i64", + "type": "payer_contract_action_id" + }, + { + "name": "payerpred", + "index_type": "i64", + "type": "payer_predicate" + }, + { + "name": "producers", + "index_type": "i64", + "type": "producer_info" + }, + { + "name": "rammarket", + "index_type": "i64", + "type": "exchange_state" + }, + { + "name": "ramoffer", + "index_type": "i64", + "type": "ram_offer" + }, + { + "name": "rampayment", + "index_type": "i64", + "type": "ram_payment" + }, + { + "name": "rampurchase", + "index_type": "i64", + "type": "ram_purchase" + }, + { + "name": "ramsponsor", + "index_type": "i64", + "type": "ram_sponsor_account" + }, + { + "name": "ramwhitelist", + "index_type": "i64", + "type": "ram_whitelist" + }, + { + "name": "refunds", + "index_type": "i64", + "type": "refund_request" + }, + { + "name": "restrictcfgs", + "index_type": "i64", + "type": "restriction_level_configs" + }, + { + "name": "restrictvote", + "index_type": "i64", + "type": "restriction_level_vote" + }, + { + "name": "tax", + "index_type": "i64", + "type": "taxes" + }, + { + "name": "unclaimed", + "index_type": "i64", + "type": "unclaimed_block" + }, + { + "name": "userres", + "index_type": "i64", + "type": "user_resources" + } + ], + "ricardian_clauses": [ + { + "id": "UserAgreement", + "body": "User agreement for the chain can go here." + }, + { + "id": "BlockProducerAgreement", + "body": "I, {{producer}}, hereby nominate myself for consideration as an elected block producer.\n\nIf {{producer}} is selected to produce blocks by the system contract, I will sign blocks with my registered block signing keys and I hereby attest that I will keep these keys secret and secure.\n\nIf {{producer}} is unable to perform obligations under this contract I will resign my position using the unregprod action.\n\nI acknowledge that a block is 'objectively valid' if it conforms to the deterministic blockchain rules in force at the time of its creation, and is 'objectively invalid' if it fails to conform to those rules.\n\n{{producer}} hereby agrees to only use my registered block signing keys to sign messages under the following scenarios:\n\n* proposing an objectively valid block at the time appointed by the block scheduling algorithm;\n* pre-confirming a block produced by another producer in the schedule when I find said block objectively valid;\n* and, confirming a block for which {{producer}} has received pre-confirmation messages from more than two-thirds of the active block producers.\n\nI hereby accept liability for any and all provable damages that result from my:\n\n* signing two different block proposals with the same timestamp;\n* signing two different block proposals with the same block number;\n* signing any block proposal which builds off of an objectively invalid block;\n* signing a pre-confirmation for an objectively invalid block;\n* or, signing a confirmation for a block for which I do not possess pre-confirmation messages from more than two-thirds of the active block producers.\n\nI hereby agree that double-signing for a timestamp or block number in concert with two or more other block producers shall automatically be deemed malicious and cause {{producer}} to be subject to:\n\n* a fine equal to the past year of compensation received,\n* immediate disqualification from being a producer,\n* and/or other damages.\n\nAn exception may be made if {{producer}} can demonstrate that the double-signing occurred due to a bug in the reference software; the burden of proof is on {{producer}}.\n\nI hereby agree not to interfere with the producer election process. I agree to process all producer election transactions that occur in blocks I create, to sign all objectively valid blocks I create that contain election transactions, and to sign all pre-confirmations and confirmations necessary to facilitate transfer of control to the next set of producers as determined by the system contract.\n\nI hereby acknowledge that more than two-thirds of the active block producers may vote to disqualify {{producer}} in the event {{producer}} is unable to produce blocks or is unable to be reached, according to criteria agreed to among block producers.\n\nIf {{producer}} qualifies for and chooses to collect compensation due to votes received, {{producer}} will provide a public endpoint allowing at least 100 peers to maintain synchronization with the blockchain and/or submit transactions to be included. {{producer}} shall maintain at least one validating node with full state and signature checking and shall report any objectively invalid blocks produced by the active block producers. Reporting shall be via a method to be agreed to among block producers, said method and reports to be made public.\n\nThe community agrees to allow {{producer}} to authenticate peers as necessary to prevent abuse and denial of service attacks; however, {{producer}} agrees not to discriminate against non-abusive peers.\n\nI agree to process transactions on a FIFO (first in, first out) best-effort basis and to honestly bill transactions for measured execution time.\n\nI {{producer}} agree not to manipulate the contents of blocks in order to derive profit from: the order in which transactions are included, or the hash of the block that is produced.\n\nI, {{producer}}, hereby agree to disclose and attest under penalty of perjury all ultimate beneficial owners of my business entity who own more than 10% and all direct shareholders.\n\nI, {{producer}}, hereby agree to cooperate with other block producers to carry out our respective and mutual obligations under this agreement, including but not limited to maintaining network stability and a valid blockchain.\n\nI, {{producer}}, agree to maintain a website hosted at {{url}} which contains up-to-date information on all disclosures required by this contract.\n\nI, {{producer}}, agree to set the location value of {{location}} such that {{producer}} is scheduled with minimal latency between my previous and next peer.\n\nI, {{producer}}, agree to maintain time synchronization within 10 ms of global atomic clock time, using a method agreed to among block producers.\n\nI, {{producer}}, agree not to produce blocks before my scheduled time unless I have received all blocks produced by the prior block producer.\n\nI, {{producer}}, agree not to publish blocks with timestamps more than 500ms in the future unless the prior block is more than 75% full by either NET or CPU bandwidth metrics.\n\nI, {{producer}}, agree not to set the RAM supply to more RAM than my nodes contain and to resign if I am unable to provide the RAM approved by more than two-thirds of active block producers, as shown in the system parameters." + } + ], + "variants": [ + { + "name": "variant_block_signing_authority_v0", + "types": [ + "block_signing_authority_v0" + ] + } + ] +} \ No newline at end of file diff --git a/testdata/eosio.nft.ft-1.31.1.abi b/testdata/eosio.nft.ft-1.31.1.abi new file mode 100644 index 0000000..f43c679 --- /dev/null +++ b/testdata/eosio.nft.ft-1.31.1.abi @@ -0,0 +1,840 @@ +{ + "version": "eosio::abi/1.1", + "types": [ + { + "new_type_name": "asset_vector", + "type": "asset[]" + }, + { + "new_type_name": "issue_token_config_vector", + "type": "issue_token_config[]" + }, + { + "new_type_name": "name_vector", + "type": "name[]" + }, + { + "new_type_name": "resale_share_vector", + "type": "resale_share[]" + }, + { + "new_type_name": "string_vector", + "type": "string[]" + }, + { + "new_type_name": "time_since_mint", + "type": "uint32" + }, + { + "new_type_name": "uint64_t_vector", + "type": "uint64[]" + } + ], + "structs": [ + { + "name": "authminter", + "base": "", + "fields": [ + { + "name": "authorizer", + "type": "name" + }, + { + "name": "authorized_minter", + "type": "name" + }, + { + "name": "token_factory_id", + "type": "uint64?" + }, + { + "name": "quantity", + "type": "uint32" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "authorized_minters_v0", + "base": "", + "fields": [ + { + "name": "authorized_minter", + "type": "name" + }, + { + "name": "quantity", + "type": "uint32" + } + ] + }, + { + "name": "burn", + "base": "", + "fields": [ + { + "name": "burn", + "type": "burn_wrap" + } + ] + }, + { + "name": "burn_wrap", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name?" + }, + { + "name": "token_ids", + "type": "uint64_t_vector?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "buy", + "base": "", + "fields": [ + { + "name": "buy", + "type": "buy_wrap" + } + ] + }, + { + "name": "buy_wrap", + "base": "", + "fields": [ + { + "name": "buyer", + "type": "name?" + }, + { + "name": "receiver", + "type": "name?" + }, + { + "name": "token_id", + "type": "uint64?" + }, + { + "name": "max_price", + "type": "asset?" + }, + { + "name": "promoter_id", + "type": "name?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "cancelresell", + "base": "", + "fields": [ + { + "name": "cancelresell", + "type": "cancelresell_wrap" + } + ] + }, + { + "name": "cancelresell_wrap", + "base": "", + "fields": [ + { + "name": "token_id", + "type": "uint64?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "clrmintst", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "no_of_entries", + "type": "uint64?" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "create", + "base": "", + "fields": [ + { + "name": "create", + "type": "create_wrap" + } + ] + }, + { + "name": "create_wrap", + "base": "", + "fields": [ + { + "name": "memo", + "type": "string?" + }, + { + "name": "version", + "type": "uint64?" + }, + { + "name": "asset_manager", + "type": "name?" + }, + { + "name": "asset_creator", + "type": "name?" + }, + { + "name": "conversion_rate_oracle_contract", + "type": "name?" + }, + { + "name": "chosen_rate", + "type": "asset_vector?" + }, + { + "name": "minimum_resell_price", + "type": "asset?" + }, + { + "name": "resale_shares", + "type": "resale_share_vector?" + }, + { + "name": "mintable_window_start", + "type": "time_point_sec?" + }, + { + "name": "mintable_window_end", + "type": "time_point_sec?" + }, + { + "name": "trading_window_start", + "type": "time_point_sec?" + }, + { + "name": "trading_window_end", + "type": "time_point_sec?" + }, + { + "name": "recall_window_start", + "type": "time_since_mint?" + }, + { + "name": "recall_window_end", + "type": "time_since_mint?" + }, + { + "name": "max_mintable_tokens", + "type": "uint32?" + }, + { + "name": "lockup_time", + "type": "uint32?" + }, + { + "name": "conditionless_receivers", + "type": "name_vector?" + }, + { + "name": "stat", + "type": "uint8?" + }, + { + "name": "meta_uris", + "type": "string_vector?" + }, + { + "name": "meta_hash", + "type": "checksum256?" + } + ] + }, + { + "name": "global_resale_share", + "base": "", + "fields": [ + { + "name": "receiver", + "type": "name" + }, + { + "name": "basis_point", + "type": "uint16" + } + ] + }, + { + "name": "globalshare", + "base": "", + "fields": [ + { + "name": "share", + "type": "uint16" + }, + { + "name": "receiver", + "type": "name?" + } + ] + }, + { + "name": "issue", + "base": "", + "fields": [ + { + "name": "issue", + "type": "issue_wrap" + } + ] + }, + { + "name": "issue_token_config", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "amount", + "type": "uint32" + }, + { + "name": "custom_data", + "type": "string" + } + ] + }, + { + "name": "issue_wrap", + "base": "", + "fields": [ + { + "name": "to", + "type": "name?" + }, + { + "name": "token_configs", + "type": "issue_token_config_vector?" + }, + { + "name": "memo", + "type": "string?" + }, + { + "name": "authorizer", + "type": "name?$" + } + ] + }, + { + "name": "limitmint", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "account_minting_limit", + "type": "uint32" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "mintstat_v0", + "base": "", + "fields": [ + { + "name": "user", + "type": "name" + }, + { + "name": "minted", + "type": "uint32" + } + ] + }, + { + "name": "next_token_factory_number", + "base": "", + "fields": [ + { + "name": "value", + "type": "uint64" + } + ] + }, + { + "name": "next_token_number", + "base": "", + "fields": [ + { + "name": "value", + "type": "uint64" + } + ] + }, + { + "name": "recall", + "base": "", + "fields": [ + { + "name": "recall", + "type": "recall_wrap" + } + ] + }, + { + "name": "recall_wrap", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name?" + }, + { + "name": "token_ids", + "type": "uint64_t_vector?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "resale_share", + "base": "", + "fields": [ + { + "name": "receiver", + "type": "name" + }, + { + "name": "basis_point", + "type": "uint16" + } + ] + }, + { + "name": "resale_v0", + "base": "", + "fields": [ + { + "name": "token_id", + "type": "uint64" + }, + { + "name": "owner", + "type": "name" + }, + { + "name": "price", + "type": "asset" + }, + { + "name": "promoter_basis_point", + "type": "uint16" + } + ] + }, + { + "name": "resell", + "base": "", + "fields": [ + { + "name": "resell", + "type": "resell_wrap" + } + ] + }, + { + "name": "resell_wrap", + "base": "", + "fields": [ + { + "name": "seller", + "type": "name?" + }, + { + "name": "token_id", + "type": "uint64?" + }, + { + "name": "price", + "type": "asset?" + }, + { + "name": "promoter_basis_point", + "type": "uint16?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "setconrecv", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64?" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "conditionless_receivers", + "type": "name_vector" + } + ] + }, + { + "name": "setmeta", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64?" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "meta_uris", + "type": "string_vector" + }, + { + "name": "meta_hash", + "type": "checksum256" + } + ] + }, + { + "name": "setstatus", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64?" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "status", + "type": "uint8" + } + ] + }, + { + "name": "token_factory_v0", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "asset_manager", + "type": "name" + }, + { + "name": "asset_creator", + "type": "name" + }, + { + "name": "conversion_rate_oracle_contract", + "type": "name" + }, + { + "name": "chosen_rate", + "type": "asset[]" + }, + { + "name": "minimum_resell_price", + "type": "asset" + }, + { + "name": "resale_shares", + "type": "resale_share[]" + }, + { + "name": "mintable_window_start", + "type": "uint32?" + }, + { + "name": "mintable_window_end", + "type": "uint32?" + }, + { + "name": "trading_window_start", + "type": "uint32?" + }, + { + "name": "trading_window_end", + "type": "uint32?" + }, + { + "name": "recall_window_start", + "type": "uint32?" + }, + { + "name": "recall_window_end", + "type": "uint32?" + }, + { + "name": "lockup_time", + "type": "uint32?" + }, + { + "name": "conditionless_receivers", + "type": "name[]" + }, + { + "name": "stat", + "type": "uint8" + }, + { + "name": "meta_uris", + "type": "string[]" + }, + { + "name": "meta_hash", + "type": "checksum256" + }, + { + "name": "max_mintable_tokens", + "type": "uint32?" + }, + { + "name": "minted_tokens_no", + "type": "uint32" + }, + { + "name": "existing_tokens_no", + "type": "uint32" + }, + { + "name": "authorized_tokens_no", + "type": "uint32?$" + }, + { + "name": "account_minting_limit", + "type": "uint32?$" + } + ] + }, + { + "name": "token_v0", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "mint_date", + "type": "time_point_sec" + }, + { + "name": "serial_number", + "type": "uint32" + } + ] + }, + { + "name": "transfer", + "base": "", + "fields": [ + { + "name": "transfer", + "type": "transfer_wrap" + } + ] + }, + { + "name": "transfer_wrap", + "base": "", + "fields": [ + { + "name": "from", + "type": "name?" + }, + { + "name": "to", + "type": "name?" + }, + { + "name": "token_ids", + "type": "uint64_t_vector?" + }, + { + "name": "memo", + "type": "string?" + } + ] + } + ], + "actions": [ + { + "name": "authminter", + "type": "authminter", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Authorize another account to mint tokens\nsummary: 'Authorize another account to mint a limited quantity of tokens or to delegate minting to yet another account.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{authorizer}} authorizes {{authorized_minter}} to mint {{quantity}} of tokens or to delegate minting of up {{quantity}} tokens to some other account. Reason: {{memo}}." + }, + { + "name": "burn", + "type": "burn", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Burn Tokens\nsummary: 'Erase the provided list of tokens from existence.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{owner}} will burn the following tokens {{ token_ids }} from existence. Reason: {{ memo }}." + }, + { + "name": "buy", + "type": "buy", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Buy Token\nsummary: 'Buy a single token listed for a sale'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{buyer}} will buy the token {{ token_id }} for a listed price and if specified a promoter {{promoter_id}} will participate in the sale share distribution. Reason: {{ memo }}." + }, + { + "name": "cancelresell", + "type": "cancelresell", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Cancel Token Resell\nsummary: 'Cancel an existing token resell opertaion'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nresell operation of the token {{token_id}} will be cancelled and it will no longer be listed for a sale. Reason: {{ memo }}." + }, + { + "name": "clrmintst", + "type": "clrmintst", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Clean mint stat\nsummary: 'Clean the existing minting status table of token factory.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nclean {{no_of_entries}}, or all entries (if {{no_of_entries}} is not specified) of the existing minting status table of token factory of id {{token_factory_id}}. Reason: {{memo}}." + }, + { + "name": "create", + "type": "create", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create Token Factory\nsummary: 'Create a new token factory.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_creator}} will create new token factory which will be managed by {{asset_manager}}. Reason: {{ memo }}." + }, + { + "name": "globalshare", + "type": "globalshare", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set global resale share\nsummary: 'Set or update global NFT token resale share'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\neach NFT token resale will additionally send a share of {{share}} basis points to the existing global share account or {{receiver}} (if specified)" + }, + { + "name": "issue", + "type": "issue", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Issue/Mint Tokens\nsummary: 'Mints a new token'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{to}} will receive the following tokens {{ token_configs }}. Memo: {{ memo }}." + }, + { + "name": "limitmint", + "type": "limitmint", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set minting limit\nsummary: 'Set the number of minting limit per account for the token factory.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nset {{account_minting_limit}} as the minting limit per account for token factory of id {{token_factory_id}}. Reason: {{memo}}." + }, + { + "name": "recall", + "type": "recall", + "ricardian_contract": "" + }, + { + "name": "resell", + "type": "resell", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Resell Token\nsummary: 'List a single token for a sale'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{seller}} will list the token {{ token_id }} for a price of {{price}} and a promoter basis point of {{promoter_basis_point}} percent. Reason: {{ memo }}." + }, + { + "name": "setconrecv", + "type": "setconrecv", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set conditionless receivers\nsummary: 'Set conditionless receivers for a token factory.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will set conditionless receivers for a factory. Reason: {{ memo }}." + }, + { + "name": "setmeta", + "type": "setmeta", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set meta\nsummary: 'Set metadata uris and metadata hash for a token factory.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will set metadata uri and metadata hash for a factory. Reason: {{ memo }}.\n\n---\nspec_version: \"0.2.0\"\ntitle: Recall Tokens\nsummary: 'Recall tokens from user to token factory manager.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will recall following tokens {{ token_ids }} from users. Reason: {{ memo }}." + }, + { + "name": "setstatus", + "type": "setstatus", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set state\nsummary: 'Set state for a token factory.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will set state for a factory. Reason: {{ memo }}." + }, + { + "name": "transfer", + "type": "transfer", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Transfer Tokens\nsummary: 'Transfer multiple tokens between accounts'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{from}} will transfer the following tokens {{ token_ids }} to {{to}} account. Reason: {{ memo }}." + } + ], + "tables": [ + { + "name": "authmintrs.a", + "index_type": "i64", + "type": "authorized_minters_v0" + }, + { + "name": "factory.a", + "index_type": "i64", + "type": "token_factory_v0" + }, + { + "name": "global.share", + "index_type": "i64", + "type": "global_resale_share" + }, + { + "name": "mintstat.a", + "index_type": "i64", + "type": "mintstat_v0" + }, + { + "name": "next.factory", + "index_type": "i64", + "type": "next_token_factory_number" + }, + { + "name": "next.token", + "index_type": "i64", + "type": "next_token_number" + }, + { + "name": "resale.a", + "index_type": "i64", + "type": "resale_v0" + }, + { + "name": "token.a", + "index_type": "i64", + "type": "token_v0" + } + ] +} \ No newline at end of file diff --git a/testdata/eosio.nft.ft-2.0.abi b/testdata/eosio.nft.ft-2.0.abi new file mode 100644 index 0000000..34b96dd --- /dev/null +++ b/testdata/eosio.nft.ft-2.0.abi @@ -0,0 +1,1302 @@ +{ + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", + "version": "eosio::abi/1.1", + "types": [ + { + "new_type_name": "asset_vector", + "type": "asset[]" + }, + { + "new_type_name": "issue_token_config_vector", + "type": "issue_token_config[]" + }, + { + "new_type_name": "minter_authorization_vector", + "type": "minter_authorization_info[]" + }, + { + "new_type_name": "name_vector", + "type": "name[]" + }, + { + "new_type_name": "resale_share_vector", + "type": "resale_share[]" + }, + { + "new_type_name": "string_vector", + "type": "string[]" + }, + { + "new_type_name": "time_since_mint", + "type": "uint32" + }, + { + "new_type_name": "uint64_t_vector", + "type": "uint64[]" + } + ], + "structs": [ + { + "name": "activers", + "base": "", + "fields": [] + }, + { + "name": "authminter", + "base": "", + "fields": [ + { + "name": "authorizer", + "type": "name" + }, + { + "name": "authorized_minter", + "type": "name" + }, + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "quantity", + "type": "uint32" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "authminter_v1", + "base": "", + "fields": [ + { + "name": "authorizer", + "type": "name" + }, + { + "name": "authorized_minter", + "type": "name" + }, + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "quantity", + "type": "uint32" + }, + { + "name": "maximum_uos_payment", + "type": "asset?" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "authorized_minters_v0", + "base": "", + "fields": [ + { + "name": "authorized_minter", + "type": "name" + }, + { + "name": "quantity", + "type": "uint32" + } + ] + }, + { + "name": "burn", + "base": "", + "fields": [ + { + "name": "burn", + "type": "burn_wrap" + } + ] + }, + { + "name": "burn_wrap", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name?" + }, + { + "name": "token_ids", + "type": "uint64_t_vector?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "buy", + "base": "", + "fields": [ + { + "name": "buy", + "type": "buy_wrap" + } + ] + }, + { + "name": "buy_wrap", + "base": "", + "fields": [ + { + "name": "buyer", + "type": "name?" + }, + { + "name": "receiver", + "type": "name?" + }, + { + "name": "token_id", + "type": "uint64?" + }, + { + "name": "max_price", + "type": "asset?" + }, + { + "name": "promoter_id", + "type": "name?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "cancelresell", + "base": "", + "fields": [ + { + "name": "cancelresell", + "type": "cancelresell_wrap" + } + ] + }, + { + "name": "cancelresell_wrap", + "base": "", + "fields": [ + { + "name": "token_id", + "type": "uint64?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "clrmintst", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "no_of_entries", + "type": "uint64?" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "create", + "base": "", + "fields": [ + { + "name": "create", + "type": "create_wrap" + } + ] + }, + { + "name": "create_v1", + "base": "", + "fields": [ + { + "name": "create", + "type": "create_wrap_v1" + } + ] + }, + { + "name": "create_wrap", + "base": "", + "fields": [ + { + "name": "memo", + "type": "string?" + }, + { + "name": "version", + "type": "uint64?" + }, + { + "name": "asset_manager", + "type": "name?" + }, + { + "name": "asset_creator", + "type": "name?" + }, + { + "name": "conversion_rate_oracle_contract", + "type": "name?" + }, + { + "name": "chosen_rate", + "type": "asset_vector?" + }, + { + "name": "minimum_resell_price", + "type": "asset?" + }, + { + "name": "resale_shares", + "type": "resale_share_vector?" + }, + { + "name": "mintable_window_start", + "type": "time_point_sec?" + }, + { + "name": "mintable_window_end", + "type": "time_point_sec?" + }, + { + "name": "trading_window_start", + "type": "time_point_sec?" + }, + { + "name": "trading_window_end", + "type": "time_point_sec?" + }, + { + "name": "recall_window_start", + "type": "time_since_mint?" + }, + { + "name": "recall_window_end", + "type": "time_since_mint?" + }, + { + "name": "max_mintable_tokens", + "type": "uint32?" + }, + { + "name": "lockup_time", + "type": "uint32?" + }, + { + "name": "conditionless_receivers", + "type": "name_vector?" + }, + { + "name": "stat", + "type": "uint8?" + }, + { + "name": "meta_uris", + "type": "string_vector?" + }, + { + "name": "meta_hash", + "type": "checksum256?" + }, + { + "name": "authorized_minters", + "type": "minter_authorization_vector$" + }, + { + "name": "account_minting_limit", + "type": "uint32$" + } + ] + }, + { + "name": "create_wrap_v1", + "base": "", + "fields": [ + { + "name": "memo", + "type": "string" + }, + { + "name": "asset_manager", + "type": "name" + }, + { + "name": "asset_creator", + "type": "name" + }, + { + "name": "minimum_resell_price", + "type": "asset?" + }, + { + "name": "resale_shares", + "type": "resale_share_vector?" + }, + { + "name": "mintable_window_start", + "type": "time_point_sec?" + }, + { + "name": "mintable_window_end", + "type": "time_point_sec?" + }, + { + "name": "trading_window_start", + "type": "time_point_sec?" + }, + { + "name": "trading_window_end", + "type": "time_point_sec?" + }, + { + "name": "recall_window_start", + "type": "time_since_mint?" + }, + { + "name": "recall_window_end", + "type": "time_since_mint?" + }, + { + "name": "max_mintable_tokens", + "type": "uint32?" + }, + { + "name": "lockup_time", + "type": "uint32?" + }, + { + "name": "conditionless_receivers", + "type": "name_vector?" + }, + { + "name": "stat", + "type": "uint8?" + }, + { + "name": "meta_uris", + "type": "string_vector?" + }, + { + "name": "meta_hash", + "type": "checksum256?" + }, + { + "name": "authorized_minters", + "type": "minter_authorization_vector?" + }, + { + "name": "account_minting_limit", + "type": "uint32?" + }, + { + "name": "transfer_window_start", + "type": "time_point_sec?" + }, + { + "name": "transfer_window_end", + "type": "time_point_sec?" + }, + { + "name": "maximum_uos_payment", + "type": "asset?" + } + ] + }, + { + "name": "global_resale_share", + "base": "", + "fields": [ + { + "name": "receiver", + "type": "name" + }, + { + "name": "basis_point", + "type": "uint16" + } + ] + }, + { + "name": "globalshare", + "base": "", + "fields": [ + { + "name": "share", + "type": "uint16" + }, + { + "name": "receiver", + "type": "name?" + } + ] + }, + { + "name": "issue", + "base": "", + "fields": [ + { + "name": "issue", + "type": "issue_wrap" + } + ] + }, + { + "name": "issue_token_config", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "amount", + "type": "uint32" + }, + { + "name": "custom_data", + "type": "string" + } + ] + }, + { + "name": "issue_v1", + "base": "", + "fields": [ + { + "name": "issue", + "type": "issue_wrap_v1" + } + ] + }, + { + "name": "issue_wrap", + "base": "", + "fields": [ + { + "name": "to", + "type": "name?" + }, + { + "name": "token_configs", + "type": "issue_token_config_vector?" + }, + { + "name": "memo", + "type": "string?" + }, + { + "name": "authorizer", + "type": "name?$" + } + ] + }, + { + "name": "issue_wrap_v1", + "base": "", + "fields": [ + { + "name": "to", + "type": "name" + }, + { + "name": "token_configs", + "type": "issue_token_config_vector" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "authorizer", + "type": "name?" + }, + { + "name": "maximum_uos_payment", + "type": "asset?" + } + ] + }, + { + "name": "limitmint", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "account_minting_limit", + "type": "uint32" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "migrate_factory", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + } + ] + }, + { + "name": "migrate_token", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "token_id", + "type": "uint64" + } + ] + }, + { + "name": "migration", + "base": "", + "fields": [ + { + "name": "active_nft_version", + "type": "uint64" + }, + { + "name": "table_migration_stats", + "type": "uint64" + } + ] + }, + { + "name": "minter_authorization_info", + "base": "", + "fields": [ + { + "name": "authorized_minter", + "type": "name" + }, + { + "name": "quantity", + "type": "uint32" + } + ] + }, + { + "name": "mintstat_v0", + "base": "", + "fields": [ + { + "name": "user", + "type": "name" + }, + { + "name": "minted", + "type": "uint32" + } + ] + }, + { + "name": "next_token_factory_number", + "base": "", + "fields": [ + { + "name": "value", + "type": "uint64" + } + ] + }, + { + "name": "next_token_number", + "base": "", + "fields": [ + { + "name": "value", + "type": "uint64" + } + ] + }, + { + "name": "ramvault_v0", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "usage", + "type": "int64" + }, + { + "name": "payment", + "type": "int64" + } + ] + }, + { + "name": "recall", + "base": "", + "fields": [ + { + "name": "recall", + "type": "recall_wrap" + } + ] + }, + { + "name": "recall_wrap", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name?" + }, + { + "name": "token_ids", + "type": "uint64_t_vector?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "resale_share", + "base": "", + "fields": [ + { + "name": "receiver", + "type": "name" + }, + { + "name": "basis_point", + "type": "uint16" + } + ] + }, + { + "name": "resale_v0", + "base": "", + "fields": [ + { + "name": "token_id", + "type": "uint64" + }, + { + "name": "owner", + "type": "name" + }, + { + "name": "price", + "type": "asset" + }, + { + "name": "promoter_basis_point", + "type": "uint16" + } + ] + }, + { + "name": "resell", + "base": "", + "fields": [ + { + "name": "resell", + "type": "resell_wrap" + } + ] + }, + { + "name": "resell_wrap", + "base": "", + "fields": [ + { + "name": "seller", + "type": "name?" + }, + { + "name": "token_id", + "type": "uint64?" + }, + { + "name": "price", + "type": "asset?" + }, + { + "name": "promoter_basis_point", + "type": "uint16?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "setconrecv", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "conditionless_receivers", + "type": "name_vector" + } + ] + }, + { + "name": "setmeta", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "meta_uris", + "type": "string_vector" + }, + { + "name": "meta_hash", + "type": "checksum256" + } + ] + }, + { + "name": "setstatus", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "status", + "type": "uint8" + } + ] + }, + { + "name": "token_factory_v0", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "asset_manager", + "type": "name" + }, + { + "name": "asset_creator", + "type": "name" + }, + { + "name": "conversion_rate_oracle_contract", + "type": "name" + }, + { + "name": "chosen_rate", + "type": "asset[]" + }, + { + "name": "minimum_resell_price", + "type": "asset" + }, + { + "name": "resale_shares", + "type": "resale_share[]" + }, + { + "name": "mintable_window_start", + "type": "uint32?" + }, + { + "name": "mintable_window_end", + "type": "uint32?" + }, + { + "name": "trading_window_start", + "type": "uint32?" + }, + { + "name": "trading_window_end", + "type": "uint32?" + }, + { + "name": "recall_window_start", + "type": "uint32?" + }, + { + "name": "recall_window_end", + "type": "uint32?" + }, + { + "name": "lockup_time", + "type": "uint32?" + }, + { + "name": "conditionless_receivers", + "type": "name[]" + }, + { + "name": "stat", + "type": "uint8" + }, + { + "name": "meta_uris", + "type": "string[]" + }, + { + "name": "meta_hash", + "type": "checksum256" + }, + { + "name": "max_mintable_tokens", + "type": "uint32?" + }, + { + "name": "minted_tokens_no", + "type": "uint32" + }, + { + "name": "existing_tokens_no", + "type": "uint32" + }, + { + "name": "authorized_tokens_no", + "type": "uint32?$" + }, + { + "name": "account_minting_limit", + "type": "uint32?$" + } + ] + }, + { + "name": "token_factory_v1", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "asset_manager", + "type": "name" + }, + { + "name": "asset_creator", + "type": "name" + }, + { + "name": "minimum_resell_price", + "type": "asset" + }, + { + "name": "resale_shares", + "type": "resale_share[]" + }, + { + "name": "mintable_window_start", + "type": "uint32?" + }, + { + "name": "mintable_window_end", + "type": "uint32?" + }, + { + "name": "trading_window_start", + "type": "uint32?" + }, + { + "name": "trading_window_end", + "type": "uint32?" + }, + { + "name": "recall_window_start", + "type": "uint32?" + }, + { + "name": "recall_window_end", + "type": "uint32?" + }, + { + "name": "lockup_time", + "type": "uint32?" + }, + { + "name": "conditionless_receivers", + "type": "name[]" + }, + { + "name": "stat", + "type": "uint8" + }, + { + "name": "meta_uris", + "type": "string[]" + }, + { + "name": "meta_hash", + "type": "checksum256" + }, + { + "name": "max_mintable_tokens", + "type": "uint32?" + }, + { + "name": "minted_tokens_no", + "type": "uint32" + }, + { + "name": "existing_tokens_no", + "type": "uint32" + }, + { + "name": "authorized_tokens_no", + "type": "uint32?" + }, + { + "name": "account_minting_limit", + "type": "uint32?" + }, + { + "name": "transfer_window_start", + "type": "uint32?" + }, + { + "name": "transfer_window_end", + "type": "uint32?" + }, + { + "name": "nft_max_ram_usage", + "type": "int64" + } + ] + }, + { + "name": "token_v0", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "mint_date", + "type": "time_point_sec" + }, + { + "name": "serial_number", + "type": "uint32" + } + ] + }, + { + "name": "token_v1", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "mint_date", + "type": "time_point_sec" + }, + { + "name": "serial_number", + "type": "uint32" + }, + { + "name": "uos_payment", + "type": "int64" + } + ] + }, + { + "name": "transfer", + "base": "", + "fields": [ + { + "name": "transfer", + "type": "transfer_wrap" + } + ] + }, + { + "name": "transfer_wrap", + "base": "", + "fields": [ + { + "name": "from", + "type": "name?" + }, + { + "name": "to", + "type": "name?" + }, + { + "name": "token_ids", + "type": "uint64_t_vector?" + }, + { + "name": "memo", + "type": "string?" + } + ] + } + ], + "actions": [ + { + "name": "activers", + "type": "activers", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: activate the new nft version\nsummary: 'activate the new nft version to migrate to'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\non-the-fly migration is activated and continuous migration will be allowed. v1 business logic becomes active." + }, + { + "name": "authmint.b", + "type": "authminter_v1", + "ricardian_contract": "" + }, + { + "name": "authminter", + "type": "authminter", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Authorize another account to mint tokens\nsummary: 'Authorize another account to mint a limited quantity of tokens or to delegate minting to yet another account.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{authorizer}} authorizes {{authorized_minter}} to mint {{quantity}} of tokens or to delegate minting of up {{quantity}} tokens to some other account. Reason: {{memo}}." + }, + { + "name": "burn", + "type": "burn", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Burn Tokens\nsummary: 'Erase the provided list of tokens from existence.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{owner}} will burn the following tokens {{ token_ids }} from existence. Reason: {{ memo }}." + }, + { + "name": "buy", + "type": "buy", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Buy Token\nsummary: 'Buy a single token listed for a sale'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{buyer}} will buy the token {{ token_id }} for a listed price and if specified a promoter {{promoter_id}} will participate in the sale share distribution. Reason: {{ memo }}." + }, + { + "name": "cancelresell", + "type": "cancelresell", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Cancel Token Resell\nsummary: 'Cancel an existing token resell opertaion'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nresell operation of the token {{token_id}} will be cancelled and it will no longer be listed for a sale. Reason: {{ memo }}." + }, + { + "name": "clrmintst", + "type": "clrmintst", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Clean mint stat\nsummary: 'Clean the existing minting status table of token factory.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nclean {{no_of_entries}}, or all entries (if {{no_of_entries}} is not specified) of the existing minting status table of token factory of id {{token_factory_id}}. Reason: {{memo}}." + }, + { + "name": "create", + "type": "create", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create a version 0 Token Factory\nsummary: 'Create a new token factory.a.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_creator}} will create new token factory which will be managed by {{asset_manager}}. Reason: {{ memo }}." + }, + { + "name": "create.b", + "type": "create_v1", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create a version 1 Token Factory\nsummary: 'Create a new token factory.b.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_creator}} will create new token factory which will be managed by {{asset_manager}}. Reason: {{ memo }}." + }, + { + "name": "globalshare", + "type": "globalshare", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set global resale share\nsummary: 'Set or update global NFT token resale share'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\neach NFT token resale will additionally send a share of {{share}} basis points to the existing global share account or {{receiver}} (if specified)" + }, + { + "name": "issue", + "type": "issue", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Issue/Mint v0 Tokens\nsummary: 'Mints a new token.a'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{to}} will receive the following tokens {{ token_configs }}. Memo: {{ memo }}." + }, + { + "name": "issue.b", + "type": "issue_v1", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Issue/Mint v1 Tokens\nsummary: 'Mints a new token.b'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{to}} will receive the following tokens {{ token_configs }}. Memo: {{ memo }}." + }, + { + "name": "limitmint", + "type": "limitmint", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set minting limit\nsummary: 'Set the number of minting limit per account for the token factory.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nset {{account_minting_limit}} as the minting limit per account for token factory of id {{token_factory_id}}. Reason: {{memo}}." + }, + { + "name": "mgrfactory", + "type": "migrate_factory", + "ricardian_contract": "" + }, + { + "name": "mgrtoken", + "type": "migrate_token", + "ricardian_contract": "" + }, + { + "name": "recall", + "type": "recall", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Recall Tokens\nsummary: 'Recall tokens from user to token factory manager.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will recall following tokens {{ token_ids }} from users. Reason: {{ memo }}." + }, + { + "name": "resell", + "type": "resell", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Resell Token\nsummary: 'List a single token for a sale'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{seller}} will list the token {{ token_id }} for a price of {{price}} and a promoter basis point of {{promoter_basis_point}} percent. Reason: {{ memo }}." + }, + { + "name": "setconrecv", + "type": "setconrecv", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set conditionless receivers\nsummary: 'Set conditionless receivers for a token factory.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will set conditionless receivers for a factory. Reason: {{ memo }}." + }, + { + "name": "setmeta", + "type": "setmeta", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set meta\nsummary: 'Set metadata uris and metadata hash for a token factory.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will set metadata uri and metadata hash for a factory. Reason: {{ memo }}." + }, + { + "name": "setstatus", + "type": "setstatus", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set state\nsummary: 'Set state for a token factory.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will set state for a factory. Reason: {{ memo }}." + }, + { + "name": "transfer", + "type": "transfer", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Transfer Tokens\nsummary: 'Transfer multiple tokens between accounts'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{from}} will transfer the following tokens {{ token_ids }} to {{to}} account. Reason: {{ memo }}." + } + ], + "tables": [ + { + "name": "authmintrs.a", + "type": "authorized_minters_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "factory.a", + "type": "token_factory_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "factory.b", + "type": "token_factory_v1", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "global.share", + "type": "global_resale_share", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "migration", + "type": "migration", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "mintstat.a", + "type": "mintstat_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "next.factory", + "type": "next_token_factory_number", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "next.token", + "type": "next_token_number", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "ramvault.a", + "type": "ramvault_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "resale.a", + "type": "resale_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "token.a", + "type": "token_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "token.b", + "type": "token_v1", + "index_type": "i64", + "key_names": [], + "key_types": [] + } + ], + "ricardian_clauses": [], + "variants": [] +} diff --git a/testdata/eosio.nft.ft-4.0.6-snapshot.abi b/testdata/eosio.nft.ft-4.0.6-snapshot.abi new file mode 100644 index 0000000..4624c68 --- /dev/null +++ b/testdata/eosio.nft.ft-4.0.6-snapshot.abi @@ -0,0 +1,3108 @@ +{ + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", + "version": "eosio::abi/1.2", + "types": [ + { + "new_type_name": "FLOAT32_VEC", + "type": "float32[]" + }, + { + "new_type_name": "FLOAT64_VEC", + "type": "float64[]" + }, + { + "new_type_name": "INT16_VEC", + "type": "int16[]" + }, + { + "new_type_name": "INT32_VEC", + "type": "int32[]" + }, + { + "new_type_name": "INT64_VEC", + "type": "int64[]" + }, + { + "new_type_name": "INT8_VEC", + "type": "bytes" + }, + { + "new_type_name": "STRING_VEC", + "type": "string[]" + }, + { + "new_type_name": "UINT16_VEC", + "type": "uint16[]" + }, + { + "new_type_name": "UINT32_VEC", + "type": "uint32[]" + }, + { + "new_type_name": "UINT64_VEC", + "type": "uint64[]" + }, + { + "new_type_name": "UINT8_VEC", + "type": "bytes" + }, + { + "new_type_name": "asset_vector", + "type": "asset[]" + }, + { + "new_type_name": "issue_token_config_vector", + "type": "issue_token_config[]" + }, + { + "new_type_name": "issue_token_metadata_vector", + "type": "issue_token_metadata[]" + }, + { + "new_type_name": "key_value_action_vec", + "type": "key_value_action[]" + }, + { + "new_type_name": "key_value_store", + "type": "variant_int8_int16_int32_int64_uint8_uint16_uint32_uint64_float32_float64_string_INT8_VEC_INT16_VEC_INT32_VEC_INT64_VEC_UINT8_VEC_UINT16_VEC_UINT32_VEC_UINT64_VEC_FLOAT32_VEC_FLOAT64_VEC_STRING_VEC" + }, + { + "new_type_name": "key_value_vec", + "type": "key_value_pair[]" + }, + { + "new_type_name": "minter_authorization_vector", + "type": "minter_authorization_info[]" + }, + { + "new_type_name": "name_vector", + "type": "name[]" + }, + { + "new_type_name": "resale_share_vector", + "type": "resale_share[]" + }, + { + "new_type_name": "string_vector", + "type": "string[]" + }, + { + "new_type_name": "time_since_mint", + "type": "uint32" + }, + { + "new_type_name": "uint64_t_vector", + "type": "uint64[]" + } + ], + "structs": [ + { + "name": "accept_factory_offer_v0", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "nft_id", + "type": "uint64" + }, + { + "name": "offer_id", + "type": "uint64" + }, + { + "name": "promoter_id", + "type": "name?" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "accept_nft_offer_v0", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "nft_id", + "type": "uint64" + }, + { + "name": "offer_id", + "type": "uint64" + }, + { + "name": "promoter_id", + "type": "name?" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "activers", + "base": "", + "fields": [] + }, + { + "name": "add_factory_keys_v0", + "base": "", + "fields": [ + { + "name": "factory_id", + "type": "uint64" + }, + { + "name": "key_defs", + "type": "key_def_action[]" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "addgrpfcts", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "factories", + "type": "uint64[]" + } + ] + }, + { + "name": "auction_config_v0", + "base": "", + "fields": [ + { + "name": "min_starting_price", + "type": "asset" + }, + { + "name": "min_duration", + "type": "uint32" + }, + { + "name": "max_duration", + "type": "uint32" + }, + { + "name": "min_bid_increment_basis_point", + "type": "uint16" + }, + { + "name": "min_bid_increment_uos", + "type": "asset" + }, + { + "name": "auction_extension_threshold", + "type": "uint32" + }, + { + "name": "auction_extension_step", + "type": "uint32" + } + ] + }, + { + "name": "authminter", + "base": "", + "fields": [ + { + "name": "authorizer", + "type": "name" + }, + { + "name": "authorized_minter", + "type": "name" + }, + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "quantity", + "type": "uint32" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "authminter_v1", + "base": "", + "fields": [ + { + "name": "authorizer", + "type": "name" + }, + { + "name": "authorized_minter", + "type": "name" + }, + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "quantity", + "type": "uint32" + }, + { + "name": "maximum_uos_payment", + "type": "asset?" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "authorized_minters_v0", + "base": "", + "fields": [ + { + "name": "authorized_minter", + "type": "name" + }, + { + "name": "quantity", + "type": "uint32" + } + ] + }, + { + "name": "bid_auction_v0", + "base": "", + "fields": [ + { + "name": "bid", + "type": "bid_auction_wrap_v0" + } + ] + }, + { + "name": "bid_auction_wrap_v0", + "base": "", + "fields": [ + { + "name": "token_id", + "type": "uint64" + }, + { + "name": "bidder", + "type": "name" + }, + { + "name": "receiver", + "type": "name?" + }, + { + "name": "bid", + "type": "asset" + }, + { + "name": "promoter_id", + "type": "name?" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "burn", + "base": "", + "fields": [ + { + "name": "burn", + "type": "burn_wrap" + } + ] + }, + { + "name": "burn_wrap", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name?" + }, + { + "name": "token_ids", + "type": "uint64_t_vector?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "buy", + "base": "", + "fields": [ + { + "name": "buy", + "type": "buy_wrap" + } + ] + }, + { + "name": "buy_offer_v0", + "base": "", + "fields": [ + { + "name": "buyer", + "type": "name" + }, + { + "name": "nft_ids", + "type": "uint64[]" + }, + { + "name": "factory_ids", + "type": "uint64[]" + } + ] + }, + { + "name": "buy_wrap", + "base": "", + "fields": [ + { + "name": "buyer", + "type": "name?" + }, + { + "name": "receiver", + "type": "name?" + }, + { + "name": "token_id", + "type": "uint64?" + }, + { + "name": "max_price", + "type": "asset?" + }, + { + "name": "promoter_id", + "type": "name?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "cancel_auction_v0", + "base": "", + "fields": [ + { + "name": "token_id", + "type": "uint64" + }, + { + "name": "canceler", + "type": "name" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "cancel_factory_offer_v0", + "base": "", + "fields": [ + { + "name": "canceler", + "type": "name" + }, + { + "name": "factory_id", + "type": "uint64" + }, + { + "name": "offer_id", + "type": "uint64" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "cancel_nft_offer_v0", + "base": "", + "fields": [ + { + "name": "canceler", + "type": "name" + }, + { + "name": "nft_id", + "type": "uint64" + }, + { + "name": "offer_id", + "type": "uint64" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "cancelresell", + "base": "", + "fields": [ + { + "name": "cancelresell", + "type": "cancelresell_wrap" + } + ] + }, + { + "name": "cancelresell_wrap", + "base": "", + "fields": [ + { + "name": "token_id", + "type": "uint64?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "cleanresell", + "base": "", + "fields": [ + { + "name": "start_id", + "type": "uint64?" + }, + { + "name": "total_number", + "type": "uint64?" + } + ] + }, + { + "name": "clrmintst", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "no_of_entries", + "type": "uint64?" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "create", + "base": "", + "fields": [ + { + "name": "create", + "type": "create_wrap" + } + ] + }, + { + "name": "create_auction_v0", + "base": "", + "fields": [ + { + "name": "create", + "type": "create_auction_wrap_v0" + } + ] + }, + { + "name": "create_auction_wrap_v0", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "token_id", + "type": "uint64" + }, + { + "name": "starting_price", + "type": "asset" + }, + { + "name": "promoter_basis_point", + "type": "uint16" + }, + { + "name": "start_date", + "type": "time_point_sec?" + }, + { + "name": "expiry_date", + "type": "time_point_sec" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "create_v1", + "base": "", + "fields": [ + { + "name": "create", + "type": "create_wrap_v1" + } + ] + }, + { + "name": "create_wrap", + "base": "", + "fields": [ + { + "name": "memo", + "type": "string?" + }, + { + "name": "version", + "type": "uint64?" + }, + { + "name": "asset_manager", + "type": "name?" + }, + { + "name": "asset_creator", + "type": "name?" + }, + { + "name": "conversion_rate_oracle_contract", + "type": "name?" + }, + { + "name": "chosen_rate", + "type": "asset_vector?" + }, + { + "name": "minimum_resell_price", + "type": "asset?" + }, + { + "name": "resale_shares", + "type": "resale_share_vector?" + }, + { + "name": "mintable_window_start", + "type": "time_point_sec?" + }, + { + "name": "mintable_window_end", + "type": "time_point_sec?" + }, + { + "name": "trading_window_start", + "type": "time_point_sec?" + }, + { + "name": "trading_window_end", + "type": "time_point_sec?" + }, + { + "name": "recall_window_start", + "type": "time_since_mint?" + }, + { + "name": "recall_window_end", + "type": "time_since_mint?" + }, + { + "name": "max_mintable_tokens", + "type": "uint32?" + }, + { + "name": "lockup_time", + "type": "uint32?" + }, + { + "name": "conditionless_receivers", + "type": "name_vector?" + }, + { + "name": "stat", + "type": "uint8?" + }, + { + "name": "meta_uris", + "type": "string_vector?" + }, + { + "name": "meta_hash", + "type": "checksum256?" + }, + { + "name": "authorized_minters", + "type": "minter_authorization_vector$" + }, + { + "name": "account_minting_limit", + "type": "uint32$" + } + ] + }, + { + "name": "create_wrap_v1", + "base": "", + "fields": [ + { + "name": "memo", + "type": "string" + }, + { + "name": "asset_manager", + "type": "name" + }, + { + "name": "asset_creator", + "type": "name" + }, + { + "name": "minimum_resell_price", + "type": "asset?" + }, + { + "name": "resale_shares", + "type": "resale_share_vector?" + }, + { + "name": "mintable_window_start", + "type": "time_point_sec?" + }, + { + "name": "mintable_window_end", + "type": "time_point_sec?" + }, + { + "name": "trading_window_start", + "type": "time_point_sec?" + }, + { + "name": "trading_window_end", + "type": "time_point_sec?" + }, + { + "name": "recall_window_start", + "type": "time_since_mint?" + }, + { + "name": "recall_window_end", + "type": "time_since_mint?" + }, + { + "name": "max_mintable_tokens", + "type": "uint32?" + }, + { + "name": "lockup_time", + "type": "uint32?" + }, + { + "name": "conditionless_receivers", + "type": "name_vector?" + }, + { + "name": "stat", + "type": "uint8?" + }, + { + "name": "factory_uri", + "type": "string?" + }, + { + "name": "factory_hash", + "type": "checksum256?" + }, + { + "name": "authorized_minters", + "type": "minter_authorization_vector?" + }, + { + "name": "account_minting_limit", + "type": "uint32?" + }, + { + "name": "transfer_window_start", + "type": "time_point_sec?" + }, + { + "name": "transfer_window_end", + "type": "time_point_sec?" + }, + { + "name": "maximum_uos_payment", + "type": "asset?" + }, + { + "name": "default_token_uri", + "type": "string?" + }, + { + "name": "default_token_hash", + "type": "checksum256?" + }, + { + "name": "lock_hash", + "type": "bool?" + } + ] + }, + { + "name": "creategrp", + "base": "", + "fields": [ + { + "name": "manager", + "type": "name" + }, + { + "name": "uri", + "type": "string" + }, + { + "name": "hash", + "type": "checksum256" + }, + { + "name": "factories", + "type": "uint64[]" + }, + { + "name": "max_uos_payment", + "type": "asset" + } + ] + }, + { + "name": "deletegrp", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + } + ] + }, + { + "name": "delprchsreq", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "index", + "type": "uint64" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "factory_group_v0", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "manager", + "type": "name" + }, + { + "name": "uri", + "type": "string" + }, + { + "name": "hash", + "type": "checksum256" + }, + { + "name": "factories", + "type": "uint64[]" + }, + { + "name": "uos_payment", + "type": "int64" + } + ] + }, + { + "name": "factory_keys", + "base": "", + "fields": [ + { + "name": "key_defs", + "type": "key_def_table[]" + }, + { + "name": "total_key_def_ram_payment_size", + "type": "int64" + }, + { + "name": "total_key_value_ram_payment_size", + "type": "int64" + } + ] + }, + { + "name": "factory_offer_v0", + "base": "", + "fields": [ + { + "name": "offer_id", + "type": "uint64" + }, + { + "name": "buyer", + "type": "name" + }, + { + "name": "receiver", + "type": "name?" + }, + { + "name": "price", + "type": "asset" + }, + { + "name": "promoter_basis_point", + "type": "uint16" + }, + { + "name": "expiry_date", + "type": "time_point_sec" + } + ] + }, + { + "name": "factory_sale_share_limit_config", + "base": "", + "fields": [ + { + "name": "max_ultra_share_bp", + "type": "uint16" + }, + { + "name": "max_factory_share_bp", + "type": "uint16" + }, + { + "name": "min_promoter_share_bp", + "type": "uint16" + }, + { + "name": "max_promoter_share_bp", + "type": "uint16" + }, + { + "name": "default_promoter", + "type": "name?" + }, + { + "name": "promoter_payments_enabled", + "type": "bool" + } + ] + }, + { + "name": "fhglobalshr", + "base": "", + "fields": [ + { + "name": "share", + "type": "uint16" + }, + { + "name": "receiver", + "type": "name?" + } + ] + }, + { + "name": "fixauthmint", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + } + ] + }, + { + "name": "global_resale_share", + "base": "", + "fields": [ + { + "name": "receiver", + "type": "name" + }, + { + "name": "basis_point", + "type": "uint16" + } + ] + }, + { + "name": "globalshare", + "base": "", + "fields": [ + { + "name": "share", + "type": "uint16" + }, + { + "name": "receiver", + "type": "name?" + } + ] + }, + { + "name": "issue", + "base": "", + "fields": [ + { + "name": "issue", + "type": "issue_wrap" + } + ] + }, + { + "name": "issue_token_config", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "amount", + "type": "uint32" + }, + { + "name": "custom_data", + "type": "string" + } + ] + }, + { + "name": "issue_token_metadata", + "base": "", + "fields": [ + { + "name": "meta_uri", + "type": "string?" + }, + { + "name": "meta_hash", + "type": "checksum256?" + } + ] + }, + { + "name": "issue_v1", + "base": "", + "fields": [ + { + "name": "issue", + "type": "issue_wrap_v1" + } + ] + }, + { + "name": "issue_wrap", + "base": "", + "fields": [ + { + "name": "to", + "type": "name?" + }, + { + "name": "token_configs", + "type": "issue_token_config_vector?" + }, + { + "name": "memo", + "type": "string?" + }, + { + "name": "authorizer", + "type": "name?$" + } + ] + }, + { + "name": "issue_wrap_v1", + "base": "", + "fields": [ + { + "name": "to", + "type": "name" + }, + { + "name": "token_configs", + "type": "issue_token_config_vector" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "authorizer", + "type": "name?" + }, + { + "name": "maximum_uos_payment", + "type": "asset?" + }, + { + "name": "token_metadata", + "type": "issue_token_metadata_vector?$" + } + ] + }, + { + "name": "key_def_action", + "base": "", + "fields": [ + { + "name": "name", + "type": "string" + }, + { + "name": "type", + "type": "string" + }, + { + "name": "edit_rights", + "type": "uint8" + }, + { + "name": "editors", + "type": "name[]" + }, + { + "name": "default_value", + "type": "key_value_store?" + } + ] + }, + { + "name": "key_def_table", + "base": "", + "fields": [ + { + "name": "name", + "type": "string" + }, + { + "name": "type_index", + "type": "uint8" + }, + { + "name": "edit_rights", + "type": "uint8" + }, + { + "name": "editors", + "type": "name[]" + }, + { + "name": "default_value", + "type": "key_value_store?" + } + ] + }, + { + "name": "key_payment", + "base": "", + "fields": [ + { + "name": "uos_payment", + "type": "int64" + } + ] + }, + { + "name": "key_type_info", + "base": "", + "fields": [ + { + "name": "key_type", + "type": "string" + }, + { + "name": "element_number_limit", + "type": "uint16" + } + ] + }, + { + "name": "key_value_action", + "base": "", + "fields": [ + { + "name": "key_name", + "type": "string" + }, + { + "name": "key_value", + "type": "key_value_store" + } + ] + }, + { + "name": "key_value_pair", + "base": "", + "fields": [ + { + "name": "key_index", + "type": "uint8" + }, + { + "name": "key_value", + "type": "key_value_store" + } + ] + }, + { + "name": "lckfactory", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + } + ] + }, + { + "name": "limitmint", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "account_minting_limit", + "type": "uint32" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "make_factory_offer_v0", + "base": "", + "fields": [ + { + "name": "buyer", + "type": "name" + }, + { + "name": "receiver", + "type": "name?" + }, + { + "name": "price", + "type": "asset" + }, + { + "name": "promoter_basis_point", + "type": "uint16" + }, + { + "name": "factory_id", + "type": "uint64" + }, + { + "name": "duration", + "type": "uint32" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "make_nft_offer_v0", + "base": "", + "fields": [ + { + "name": "buyer", + "type": "name" + }, + { + "name": "receiver", + "type": "name?" + }, + { + "name": "price", + "type": "asset" + }, + { + "name": "promoter_basis_point", + "type": "uint16" + }, + { + "name": "owner", + "type": "name" + }, + { + "name": "nft_id", + "type": "uint64" + }, + { + "name": "duration", + "type": "uint32" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "migrate_factories", + "base": "", + "fields": [ + { + "name": "total_no", + "type": "uint64" + } + ] + }, + { + "name": "migrate_tokens", + "base": "", + "fields": [ + { + "name": "owners", + "type": "name_vector" + }, + { + "name": "total_no", + "type": "uint64" + } + ] + }, + { + "name": "migration", + "base": "", + "fields": [ + { + "name": "active_nft_version", + "type": "uint64" + }, + { + "name": "table_migration_stats", + "type": "uint64" + } + ] + }, + { + "name": "minter_authorization_info", + "base": "", + "fields": [ + { + "name": "authorized_minter", + "type": "name" + }, + { + "name": "quantity", + "type": "uint32" + } + ] + }, + { + "name": "mintstat_v0", + "base": "", + "fields": [ + { + "name": "user", + "type": "name" + }, + { + "name": "minted", + "type": "uint32" + } + ] + }, + { + "name": "next_factory_offer_number", + "base": "", + "fields": [ + { + "name": "value", + "type": "uint64" + } + ] + }, + { + "name": "next_fct_group_id", + "base": "", + "fields": [ + { + "name": "value", + "type": "uint64" + } + ] + }, + { + "name": "next_nft_auction_number", + "base": "", + "fields": [ + { + "name": "value", + "type": "uint64" + } + ] + }, + { + "name": "next_nft_offer_number", + "base": "", + "fields": [ + { + "name": "value", + "type": "uint64" + } + ] + }, + { + "name": "next_token_factory_number", + "base": "", + "fields": [ + { + "name": "value", + "type": "uint64" + } + ] + }, + { + "name": "next_token_number", + "base": "", + "fields": [ + { + "name": "value", + "type": "uint64" + } + ] + }, + { + "name": "nft_auction_v0", + "base": "", + "fields": [ + { + "name": "token_id", + "type": "uint64" + }, + { + "name": "auction_id", + "type": "uint64" + }, + { + "name": "owner", + "type": "name" + }, + { + "name": "bid", + "type": "asset" + }, + { + "name": "bidder", + "type": "name?" + }, + { + "name": "receiver", + "type": "name?" + }, + { + "name": "promoter_id", + "type": "name?" + }, + { + "name": "promoter_basis_point", + "type": "uint16" + }, + { + "name": "start_date", + "type": "time_point_sec?" + }, + { + "name": "expiry_date", + "type": "time_point_sec" + } + ] + }, + { + "name": "nft_offer_v0", + "base": "", + "fields": [ + { + "name": "offer_id", + "type": "uint64" + }, + { + "name": "buyer", + "type": "name" + }, + { + "name": "receiver", + "type": "name?" + }, + { + "name": "price", + "type": "asset" + }, + { + "name": "promoter_basis_point", + "type": "uint16" + }, + { + "name": "expiry_date", + "type": "time_point_sec" + } + ] + }, + { + "name": "offer_config_v0", + "base": "", + "fields": [ + { + "name": "min_price", + "type": "asset" + }, + { + "name": "min_duration", + "type": "uint32" + }, + { + "name": "max_duration", + "type": "uint32" + }, + { + "name": "max_active_offer_per_user", + "type": "uint32" + } + ] + }, + { + "name": "provided_user_uniqs", + "base": "", + "fields": [ + { + "name": "tokens", + "type": "uniqs_count_internal[]" + } + ] + }, + { + "name": "purchase", + "base": "", + "fields": [ + { + "name": "purchase", + "type": "purchase_wrap" + } + ] + }, + { + "name": "purchase_requirement_with_uniqs", + "base": "", + "fields": [ + { + "name": "transfer_tokens_receiver_account", + "type": "name?" + }, + { + "name": "factories", + "type": "uniqs_count[]" + } + ] + }, + { + "name": "purchase_wrap", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "index", + "type": "uint64" + }, + { + "name": "max_price", + "type": "asset" + }, + { + "name": "buyer", + "type": "name" + }, + { + "name": "receiver", + "type": "name" + }, + { + "name": "promoter_id", + "type": "name?" + }, + { + "name": "user_uniqs", + "type": "provided_user_uniqs?" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "ramvault_v0", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "usage", + "type": "int64" + }, + { + "name": "payment", + "type": "int64" + } + ] + }, + { + "name": "recall", + "base": "", + "fields": [ + { + "name": "recall", + "type": "recall_wrap" + } + ] + }, + { + "name": "recall_wrap", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name?" + }, + { + "name": "token_ids", + "type": "uint64_t_vector?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "resale_share", + "base": "", + "fields": [ + { + "name": "receiver", + "type": "name" + }, + { + "name": "basis_point", + "type": "uint16" + } + ] + }, + { + "name": "resale_v0", + "base": "", + "fields": [ + { + "name": "token_id", + "type": "uint64" + }, + { + "name": "owner", + "type": "name" + }, + { + "name": "price", + "type": "asset" + }, + { + "name": "promoter_basis_point", + "type": "uint16" + } + ] + }, + { + "name": "resell", + "base": "", + "fields": [ + { + "name": "resell", + "type": "resell_wrap" + } + ] + }, + { + "name": "resell_wrap", + "base": "", + "fields": [ + { + "name": "seller", + "type": "name?" + }, + { + "name": "token_id", + "type": "uint64?" + }, + { + "name": "price", + "type": "asset?" + }, + { + "name": "promoter_basis_point", + "type": "uint16?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "rmgrpfcts", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "factories", + "type": "uint64[]" + } + ] + }, + { + "name": "set_auction_config_v0", + "base": "", + "fields": [ + { + "name": "config", + "type": "set_auction_config_wrap_v0" + } + ] + }, + { + "name": "set_auction_config_wrap_v0", + "base": "", + "fields": [ + { + "name": "min_starting_price", + "type": "asset?" + }, + { + "name": "min_duration", + "type": "uint32?" + }, + { + "name": "max_duration", + "type": "uint32?" + }, + { + "name": "min_bid_increment_basis_point", + "type": "uint16?" + }, + { + "name": "min_bid_increment_uos", + "type": "asset?" + }, + { + "name": "auction_extension_threshold", + "type": "uint32?" + }, + { + "name": "auction_extension_step", + "type": "uint32?" + } + ] + }, + { + "name": "set_key_types", + "base": "", + "fields": [ + { + "name": "key_types", + "type": "key_type_info[]" + } + ] + }, + { + "name": "set_offer_config_v0", + "base": "", + "fields": [ + { + "name": "min_price", + "type": "asset?" + }, + { + "name": "min_duration", + "type": "uint32?" + }, + { + "name": "max_duration", + "type": "uint32?" + }, + { + "name": "max_active_offer_per_user", + "type": "uint32?" + } + ] + }, + { + "name": "set_purchase_requirements_wrap", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "index", + "type": "uint64" + }, + { + "name": "price", + "type": "asset" + }, + { + "name": "purchase_limit", + "type": "uint32?" + }, + { + "name": "promoter_basis_point", + "type": "uint16" + }, + { + "name": "purchase_option_with_uniqs", + "type": "purchase_requirement_with_uniqs?" + }, + { + "name": "sale_shares", + "type": "resale_share[]" + }, + { + "name": "maximum_uos_payment", + "type": "asset?" + }, + { + "name": "group_restriction", + "type": "uint64_t_vector?" + }, + { + "name": "purchase_window_start", + "type": "time_point_sec?" + }, + { + "name": "purchase_window_end", + "type": "time_point_sec?" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "set_purchase_requirements_wrap_v1", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "index", + "type": "uint64" + }, + { + "name": "price", + "type": "asset" + }, + { + "name": "purchase_limit", + "type": "uint32?" + }, + { + "name": "promoter_basis_point", + "type": "uint16" + }, + { + "name": "purchase_option_with_uniqs", + "type": "purchase_requirement_with_uniqs?" + }, + { + "name": "sale_shares", + "type": "resale_share[]" + }, + { + "name": "maximum_uos_payment", + "type": "asset?" + }, + { + "name": "group_restriction", + "type": "string" + }, + { + "name": "purchase_window_start", + "type": "time_point_sec?" + }, + { + "name": "purchase_window_end", + "type": "time_point_sec?" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "set_trade_window", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "trading_window_start", + "type": "time_point_sec?" + }, + { + "name": "trading_window_end", + "type": "time_point_sec?" + } + ] + }, + { + "name": "set_transfer_window", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "transfer_window_start", + "type": "time_point_sec?" + }, + { + "name": "transfer_window_end", + "type": "time_point_sec?" + } + ] + }, + { + "name": "set_uniq_value_v0", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "editor", + "type": "name?" + }, + { + "name": "token_id", + "type": "uint64" + }, + { + "name": "key_values", + "type": "key_value_action_vec" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "setconrecv", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "conditionless_receivers", + "type": "name_vector" + } + ] + }, + { + "name": "setdflttkn", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "uri", + "type": "string" + }, + { + "name": "hash", + "type": "checksum256?" + } + ] + }, + { + "name": "setmeta", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "meta_uris", + "type": "string_vector" + }, + { + "name": "meta_hash", + "type": "checksum256" + } + ] + }, + { + "name": "setmeta_v1", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "factory_uri", + "type": "string" + }, + { + "name": "factory_hash", + "type": "checksum256" + } + ] + }, + { + "name": "setnftmgrflg", + "base": "", + "fields": [] + }, + { + "name": "setprchsreq", + "base": "", + "fields": [ + { + "name": "purchase_option", + "type": "set_purchase_requirements_wrap" + } + ] + }, + { + "name": "setprchsreq_v1", + "base": "", + "fields": [ + { + "name": "purchase_option", + "type": "set_purchase_requirements_wrap_v1" + } + ] + }, + { + "name": "setsharelim", + "base": "", + "fields": [ + { + "name": "type", + "type": "uint8" + }, + { + "name": "config", + "type": "factory_sale_share_limit_config" + } + ] + }, + { + "name": "setstatus", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "status", + "type": "uint8" + } + ] + }, + { + "name": "settfflag", + "base": "", + "fields": [] + }, + { + "name": "settknmeta", + "base": "", + "fields": [ + { + "name": "token_id", + "type": "uint64" + }, + { + "name": "owner", + "type": "name" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "uri", + "type": "string?" + }, + { + "name": "hash", + "type": "checksum256?" + } + ] + }, + { + "name": "settle_auction_v0", + "base": "", + "fields": [ + { + "name": "token_id", + "type": "uint64" + }, + { + "name": "executer", + "type": "name" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "supported_key_types", + "base": "", + "fields": [ + { + "name": "key_types", + "type": "key_type_info[]" + } + ] + }, + { + "name": "tf_create_flags_type", + "base": "", + "fields": [ + { + "name": "require_ultra", + "type": "bool" + } + ] + }, + { + "name": "token_factory_purchase_v0", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "price", + "type": "asset" + }, + { + "name": "purchase_limit", + "type": "uint32?" + }, + { + "name": "purchased_tokens_no", + "type": "uint32" + }, + { + "name": "promoter_basis_point", + "type": "uint16" + }, + { + "name": "purchase_option_with_uniqs", + "type": "purchase_requirement_with_uniqs?" + }, + { + "name": "sale_shares", + "type": "resale_share[]" + }, + { + "name": "uos_payment", + "type": "int64" + }, + { + "name": "group_restriction", + "type": "uint64_t_vector?" + }, + { + "name": "purchase_window_start", + "type": "time_point_sec?" + }, + { + "name": "purchase_window_end", + "type": "time_point_sec?" + } + ] + }, + { + "name": "token_factory_v0", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "asset_manager", + "type": "name" + }, + { + "name": "asset_creator", + "type": "name" + }, + { + "name": "conversion_rate_oracle_contract", + "type": "name" + }, + { + "name": "chosen_rate", + "type": "asset[]" + }, + { + "name": "minimum_resell_price", + "type": "asset" + }, + { + "name": "resale_shares", + "type": "resale_share[]" + }, + { + "name": "mintable_window_start", + "type": "uint32?" + }, + { + "name": "mintable_window_end", + "type": "uint32?" + }, + { + "name": "trading_window_start", + "type": "uint32?" + }, + { + "name": "trading_window_end", + "type": "uint32?" + }, + { + "name": "recall_window_start", + "type": "uint32?" + }, + { + "name": "recall_window_end", + "type": "uint32?" + }, + { + "name": "lockup_time", + "type": "uint32?" + }, + { + "name": "conditionless_receivers", + "type": "name[]" + }, + { + "name": "stat", + "type": "uint8" + }, + { + "name": "meta_uris", + "type": "string[]" + }, + { + "name": "meta_hash", + "type": "checksum256" + }, + { + "name": "max_mintable_tokens", + "type": "uint32?" + }, + { + "name": "minted_tokens_no", + "type": "uint32" + }, + { + "name": "existing_tokens_no", + "type": "uint32" + }, + { + "name": "authorized_tokens_no", + "type": "uint32?$" + }, + { + "name": "account_minting_limit", + "type": "uint32?$" + } + ] + }, + { + "name": "token_factory_v1", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "asset_manager", + "type": "name" + }, + { + "name": "asset_creator", + "type": "name" + }, + { + "name": "minimum_resell_price", + "type": "asset" + }, + { + "name": "resale_shares", + "type": "resale_share[]" + }, + { + "name": "mintable_window_start", + "type": "uint32?" + }, + { + "name": "mintable_window_end", + "type": "uint32?" + }, + { + "name": "trading_window_start", + "type": "uint32?" + }, + { + "name": "trading_window_end", + "type": "uint32?" + }, + { + "name": "recall_window_start", + "type": "uint32?" + }, + { + "name": "recall_window_end", + "type": "uint32?" + }, + { + "name": "lockup_time", + "type": "uint32?" + }, + { + "name": "conditionless_receivers", + "type": "name[]" + }, + { + "name": "stat", + "type": "uint8" + }, + { + "name": "factory_uri", + "type": "string" + }, + { + "name": "factory_hash", + "type": "checksum256" + }, + { + "name": "max_mintable_tokens", + "type": "uint32?" + }, + { + "name": "minted_tokens_no", + "type": "uint32" + }, + { + "name": "existing_tokens_no", + "type": "uint32" + }, + { + "name": "authorized_tokens_no", + "type": "uint32?" + }, + { + "name": "account_minting_limit", + "type": "uint32?" + }, + { + "name": "transfer_window_start", + "type": "uint32?" + }, + { + "name": "transfer_window_end", + "type": "uint32?" + }, + { + "name": "default_token_uri", + "type": "string" + }, + { + "name": "default_token_hash", + "type": "checksum256?" + }, + { + "name": "lock_hash", + "type": "bool" + }, + { + "name": "keys", + "type": "factory_keys?$" + } + ] + }, + { + "name": "token_v0", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "mint_date", + "type": "time_point_sec" + }, + { + "name": "serial_number", + "type": "uint32" + } + ] + }, + { + "name": "token_v1", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "mint_date", + "type": "time_point_sec" + }, + { + "name": "serial_number", + "type": "uint32" + }, + { + "name": "uos_payment", + "type": "int64" + }, + { + "name": "uri", + "type": "string?" + }, + { + "name": "hash", + "type": "checksum256?" + }, + { + "name": "key_values", + "type": "key_value_vec?$" + } + ] + }, + { + "name": "transfer", + "base": "", + "fields": [ + { + "name": "transfer", + "type": "transfer_wrap" + } + ] + }, + { + "name": "transfer_wrap", + "base": "", + "fields": [ + { + "name": "from", + "type": "name?" + }, + { + "name": "to", + "type": "name?" + }, + { + "name": "token_ids", + "type": "uint64_t_vector?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "uniqs_count", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "count", + "type": "uint32" + }, + { + "name": "strategy", + "type": "uint8" + } + ] + }, + { + "name": "uniqs_count_internal", + "base": "", + "fields": [ + { + "name": "token_id", + "type": "uint64" + }, + { + "name": "strategy", + "type": "uint8" + } + ] + }, + { + "name": "updategrp", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "uri", + "type": "string?" + }, + { + "name": "hash", + "type": "checksum256?" + }, + { + "name": "factories", + "type": "uint64_t_vector?" + } + ] + } + ], + "actions": [ + { + "name": "acptfctofr.a", + "type": "accept_factory_offer_v0", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Accept the buy offer on a NFT (token) of the NFT factory\nsummary: 'Accept the buy offer on a NFT (token) of the NFT factory.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{owner}} accepts the buy offer specified by {{offer_id}} on the NFT specified by {{nft_id}}. Reason: {{ memo }}. {{owner}} can specify {{promoter_id}}. The offer should have been made to any NFT on a factory and {{ower]] accepts one NFT from the factory." + }, + { + "name": "acptnftofr.a", + "type": "accept_nft_offer_v0", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Accept the buy offer on a NFT (token)\nsummary: 'Accept the buy offer on a NFT (token).'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{owner}} accepts the buy offer specified by {{offer_id}} on the NFT specified by {{nft_id}}. Reason: {{ memo }}. {{owner}} can specify {{promoter_id}}." + }, + { + "name": "activers", + "type": "activers", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: activate the new nft version\nsummary: 'activate the new nft version to migrate to'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\non-the-fly migration is activated and continuous migration will be allowed. v1 business logic becomes active." + }, + { + "name": "addgrpfcts", + "type": "addgrpfcts", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Adds factories to a factory group\nsummary: 'Adds factories to a factory group'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nExtends group {{id}} with {{factories}}." + }, + { + "name": "addkeys.a", + "type": "add_factory_keys_v0", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Add factory keys\nsummary: 'Add factory keys'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nAdd {{ key_defs }} to factory with ID {{ factory_id }}. Reason: {{ memo }}" + }, + { + "name": "authmint.b", + "type": "authminter_v1", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Authorize another account to mint v1 tokens\nsummary: 'Authorize another account to mint a limited quantity of tokens or to delegate minting to yet another account.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{authorizer}} authorizes {{authorized_minter}} to mint {{quantity}} of tokens or to delegate minting of up {{quantity}} tokens to some other account. Reason: {{memo}}." + }, + { + "name": "authminter", + "type": "authminter", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Authorize another account to mint tokens\nsummary: 'Authorize another account to mint a limited quantity of tokens or to delegate minting to yet another account.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{authorizer}} authorizes {{authorized_minter}} to mint {{quantity}} of tokens or to delegate minting of up {{quantity}} tokens to some other account. Reason: {{memo}}." + }, + { + "name": "bidauction.a", + "type": "bid_auction_v0", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Place a bid on a NFT auction\nsummary: 'Place a bid on a NFT auction.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nBidder places a UOS bid on an existing NFT auction and optionally specifies the receiver and promoter accounts." + }, + { + "name": "burn", + "type": "burn", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Burn Tokens\nsummary: 'Erase the provided list of tokens from existence.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{owner}} will burn the following tokens {{ token_ids }} from existence. Reason: {{ memo }}." + }, + { + "name": "buy", + "type": "buy", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Buy Token\nsummary: 'Buy a single token listed for a sale'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{buyer}} will buy the token {{ token_id }} for a listed price and if specified a promoter {{promoter_id}} will participate in the sale share distribution. Reason: {{ memo }}." + }, + { + "name": "cancelauct.a", + "type": "cancel_auction_v0", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Cancel a NFT auction\nsummary: 'Cancel a NFT action that has no bids.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{canceler}} cancels an auction for the NFT with ID {{token_id}} that has no active bids. Reason: {{ memo }}." + }, + { + "name": "cancelresell", + "type": "cancelresell", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Cancel Token Resell\nsummary: 'Cancel an existing token resell opertaion'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nresell operation of the token {{token_id}} will be cancelled and it will no longer be listed for a sale. Reason: {{ memo }}." + }, + { + "name": "cleanresell", + "type": "cleanresell", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle:clean invalid resell entries\nsummary: 'scan given number of resales and clean the entry where owner doesnot own the token anymore'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nStarting from the token with id {{start_id}} scan up to {{total_number}} tokens and remove any entry where its owner does not own the token anymore." + }, + { + "name": "clrmintst", + "type": "clrmintst", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Clean mint stat\nsummary: 'Clean the existing minting status table of token factory.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nclean {{no_of_entries}}, or all entries (if {{no_of_entries}} is not specified) of the existing minting status table of token factory of id {{token_factory_id}}. Reason: {{memo}}." + }, + { + "name": "create", + "type": "create", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create a version 0 Token Factory\nsummary: 'Create a new token factory.a.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_creator}} will create new token factory which will be managed by {{asset_manager}}. Reason: {{ memo }}." + }, + { + "name": "create.b", + "type": "create_v1", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create a version 1 Token Factory\nsummary: 'Create a new token factory.b.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_creator}} will create new token factory which will be managed by {{asset_manager}}. Reason: {{ memo }}." + }, + { + "name": "createauct.a", + "type": "create_auction_v0", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create an auction for a NFT\nsummary: 'Create an auction for a NFT.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nOwner of a NFT creates an auction with a specified starting price and expiry date." + }, + { + "name": "creategrp", + "type": "creategrp", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create a token factory Group\nsummary: 'Create a new token factory group'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{manager}} creates a new token factory group with {{uri}}, {{hash}}, {{factories}} and {{max_uos_payment}}." + }, + { + "name": "deletegrp", + "type": "deletegrp", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Delete a token factory Group\nsummary: 'Delete a new token factory group'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nDeletes a factory group {{id}}." + }, + { + "name": "delprchsreq.a", + "type": "delprchsreq", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Delete purchase requirements\nsummary: 'Delete purchase requirements'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n{{asset_manager}} will be able to update purchase requirements for a token factory." + }, + { + "name": "fhglobalshr", + "type": "fhglobalshr", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set global first-hand purchase share\nsummary: 'Set or update global NFT token first-hand purchase share'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nEach NFT token direct purchase will additionally send a share of {{share}} basis points to the existing global share account or {{receiver}} (if specified)" + }, + { + "name": "fixauthmint", + "type": "fixauthmint", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Fix a fault tokenn factory\nsummary: 'Fix a token factory with inconsistent number of minted tokens and reserved tokens for authminters.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nFix a token factory of id {{token_factory_id}} with inconsistent number of minted tokens and reserved tokens for authminters." + }, + { + "name": "globalshare", + "type": "globalshare", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set global resale share\nsummary: 'Set or update global NFT token resale share'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\neach NFT token resale will additionally send a share of {{share}} basis points to the existing global share account or {{receiver}} (if specified)" + }, + { + "name": "issue", + "type": "issue", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Issue/Mint v0 Tokens\nsummary: 'Mints a new token.a'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{to}} will receive the following tokens {{ token_configs }}. Memo: {{ memo }}." + }, + { + "name": "issue.b", + "type": "issue_v1", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Issue/Mint v1 Tokens\nsummary: 'Mints a new token.b'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{to}} will receive the following tokens {{ token_configs }}. Memo: {{ memo }}." + }, + { + "name": "lckfactory", + "type": "lckfactory", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Lock factory\nsummary: 'Lock a factory and prevent changing the hash.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nSet a token factory status with id {{token_factory_id}} to not allow any more changes to hashes and URIs." + }, + { + "name": "limitmint", + "type": "limitmint", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set minting limit\nsummary: 'Set the number of minting limit per account for the token factory.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nset {{account_minting_limit}} as the minting limit per account for token factory of id {{token_factory_id}}. Reason: {{memo}}." + }, + { + "name": "mgrfactories", + "type": "migrate_factories", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Migrate factories\nsummary: 'Migrate token factory table entries.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nMigrate {{total_no}} of token factory table entries, or all the remaining entries if they are less than {{total_no}}." + }, + { + "name": "mgrnfts", + "type": "migrate_tokens", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Migrate tokens\nsummary: 'Migrate token table entries.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nMigrate {{total_no}} of {{owners}}' token table entries, or all the remaining entries if they are less than {{total_no}}." + }, + { + "name": "mkfctofr.a", + "type": "make_factory_offer_v0", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Make a buy offer on any NFT (token) of a NFT factory\nsummary: 'Make a buy offer on any NFT (token) of a NFT factory.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{buyer}} makes a buy offer on any NFT of a NFT factory specified by {{factory_id}} with {{price}}, {{promoter_basis_point}}, and {{duration}}. Reason: {{ memo }}. {{buyer}} can specify another {{receiver}} to receive the NFT." + }, + { + "name": "mknftofr.a", + "type": "make_nft_offer_v0", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Make a buy offer on a NFT (token)\nsummary: 'Make a buy offer on a NFT (token).'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{buyer}} makes a buy offer on a NFT specified by {{nft_id}} of {{owner}} with {{price}}, {{promoter_basis_point}}, and {{duration}}. Reason: {{ memo }}. {{buyer}} can specify another {{receiver}} to receive the NFT." + }, + { + "name": "purchase.a", + "type": "purchase", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Purchase a Uniq onchain\nsummary: 'Allows a user to purchase a Uniq onchain'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\nUsers will be able to directly purchase a uniq from a token factory." + }, + { + "name": "recall", + "type": "recall", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Recall Tokens\nsummary: 'Recall tokens from user to token factory manager.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will recall following tokens {{ token_ids }} from users. Reason: {{ memo }}." + }, + { + "name": "resell", + "type": "resell", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Resell Token\nsummary: 'List a single token for a sale'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{seller}} will list the token {{ token_id }} for a price of {{price}} and a promoter basis point of {{promoter_basis_point}} percent. Reason: {{ memo }}." + }, + { + "name": "rmfctofr.a", + "type": "cancel_factory_offer_v0", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Cancel the buy offer on a NFT\nsummary: 'Cancel the buy offer on a NFT.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{canceler}} cancels the buy offer specified by {{offer_id}} on the NFT factory specified by {{factory_id}}. Reason: {{ memo }}." + }, + { + "name": "rmgrpfcts", + "type": "rmgrpfcts", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Remove factories from token factory Group\nsummary: 'Removes factories from a factory group'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nRemoves a list of {{factories}} from a group {{id}}." + }, + { + "name": "rmnftofr.a", + "type": "cancel_nft_offer_v0", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Cancel the buy offer on a NFT (token)\nsummary: 'Cancel the buy offer on a NFT (token).'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{canceler}} cancels the buy offer specified by {{offer_id}} on the NFT specified by {{nft_id}}. Reason: {{ memo }}." + }, + { + "name": "setconrecv", + "type": "setconrecv", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set conditionless receivers\nsummary: 'Set conditionless receivers for a token factory.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will set conditionless receivers for a factory. Reason: {{ memo }}." + }, + { + "name": "setdflttkn", + "type": "setdflttkn", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set default token data\nsummary: 'Set a URI and hash for default token metadata.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nUpdate default token metadata information for factory with id {{token_factory_id}} to change URI and/or hash. Reson: {{memo}}." + }, + { + "name": "setktypes", + "type": "set_key_types", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set supported key types\nsummary: 'Set supported types for factory data keys.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nSet supported {{ key_types }} for factory data keys." + }, + { + "name": "setmeta", + "type": "setmeta", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set meta\nsummary: 'Set metadata uris and metadata hash for a token factory.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will set metadata uri and metadata hash for a factory. Reason: {{ memo }}." + }, + { + "name": "setmeta.b", + "type": "setmeta_v1", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set meta\nsummary: 'Set metadata uris and metadata hash for a token factory.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will set metadata uri and metadata hash for a factory. Reason: {{ memo }}." + }, + { + "name": "setnftmgrflg", + "type": "setnftmgrflg", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set token migration done flag\nsummary: 'Set token migration done flag.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nSet token migration done flag in migration table." + }, + { + "name": "setprchsreq.a", + "type": "setprchsreq", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set purchase requirements\nsummary: 'Create/Update purchase requirements'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n{{asset_manager}} will be able to update purchase requirements for a token factory." + }, + { + "name": "setprchsreq.b", + "type": "setprchsreq_v1", + "ricardian_contract": "" + }, + { + "name": "setsharelim", + "type": "setsharelim", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set factory sale share limits\nsummary: 'Set global limit on all sales done from a factory for ultra, promoter and factory itself'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nSets limits for NFT token sales which will limit any possible share for Ultra to {{max_ultra_share_bp}}, for factory beneficiaries to {{max_factory_share_bp}} and for promoter to {{max_promoter_share_bp}}. Additionally the lowest share promoter can receive is {{max_promoter_share_bp}} and in case no promoter is specified the following promoter will be used {{default_promoter}}" + }, + { + "name": "setstatus", + "type": "setstatus", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set state\nsummary: 'Set state for a token factory.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will set state for a factory. Reason: {{ memo }}." + }, + { + "name": "settfflag", + "type": "settfflag", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set token factory creation permission\nsummary: 'Sets token factory creation permission flag to allow any account to create token factories.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nSets token factory creation permission flag to allow any account to create token factories." + }, + { + "name": "settknmeta", + "type": "settknmeta", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set token data\nsummary: 'Set a URI and hash for token metadata.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nUpdate token metadata information with id {{token_id}} of a user {{user}} to change URI and/or hash. Reson: {{memo}}." + }, + { + "name": "settleauct.a", + "type": "settle_auction_v0", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Settle a NFT auction\nsummary: 'Settle a NFT action that reached its expiration date.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{executer}} concludes an auction for the NFT with ID {{token_id}}. {{token_id}} will be transferred to the highest bidder or receiver account and the bid will be shared between auction creator, promoter and factory resale shares. Reason: {{ memo }}." + }, + { + "name": "settrdwin.a", + "type": "set_trade_window", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set trade window\nsummary: 'Update trading window of a token factory'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n{{asset_manager}} will update the {{trading_window_start}} and {{trading_window_end}} for a token factory." + }, + { + "name": "settrnfwin.a", + "type": "set_transfer_window", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set transfer window\nsummary: 'Update transfer window of a token factory'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n{{asset_manager}} will update the {{transfer_window_start}} and {{transfer_window_end}} for a token factory." + }, + { + "name": "setvals.a", + "type": "set_uniq_value_v0", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set uniq value\nsummary: 'Set uniq value'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nUpdate uniq with id {{ token_id }} from {{ owner }} with {{ key_values }}. Reason: {{ memo }}" + }, + { + "name": "stauctcfg.a", + "type": "set_auction_config_v0", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set or update the version 0 NFT auction configurations\nsummary: 'Set or update NFT auction configurations in auctioncfg.a.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nultra sets or updates the NFT auction configurations including starting auction price, min/max duration of an auction, min bid increments and auction duration extension" + }, + { + "name": "stofrcfg.a", + "type": "set_offer_config_v0", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set or update the version 0 buy offer configurations\nsummary: 'Set or update buy offer configurations in stofrcfg.a.'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nultra sets or updates the buy offer configurations including min./max. durations and max. number of active offers per user" + }, + { + "name": "transfer", + "type": "transfer", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Transfer Tokens\nsummary: 'Transfer multiple tokens between accounts'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{from}} will transfer the following tokens {{ token_ids }} to {{to}} account. Reason: {{ memo }}." + }, + { + "name": "updategrp", + "type": "updategrp", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Update a token factory Group\nsummary: 'Update a new token factory group'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nAllows factory group {{id}} to be updated. At least one of {{uri}}, {{hash}}, {{factories}}\nshould be specified." + } + ], + "tables": [ + { + "name": "auction.a", + "type": "nft_auction_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "auctioncfg.a", + "type": "auction_config_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "authmintrs.a", + "type": "authorized_minters_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "buyoffer.a", + "type": "buy_offer_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "factory.a", + "type": "token_factory_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "factory.b", + "type": "token_factory_v1", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "factorygrp.a", + "type": "factory_group_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "fctoffer.a", + "type": "factory_offer_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "fctrprchs.a", + "type": "token_factory_purchase_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "global.share", + "type": "global_resale_share", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "keypay.a", + "type": "key_payment", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "keytypes", + "type": "supported_key_types", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "migration", + "type": "migration", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "mintstat.a", + "type": "mintstat_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "next.factory", + "type": "next_token_factory_number", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "next.fct.grp", + "type": "next_fct_group_id", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "next.fctofr", + "type": "next_factory_offer_number", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "next.nftauc", + "type": "next_nft_auction_number", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "next.nftofr", + "type": "next_nft_offer_number", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "next.token", + "type": "next_token_number", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "nftoffer.a", + "type": "nft_offer_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "offercfg.a", + "type": "offer_config_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "ramvault.a", + "type": "ramvault_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "resale.a", + "type": "resale_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "saleshrlmcfg", + "type": "factory_sale_share_limit_config", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "tfcreateflag", + "type": "tf_create_flags_type", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "token.a", + "type": "token_v0", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "token.b", + "type": "token_v1", + "index_type": "i64", + "key_names": [], + "key_types": [] + } + ], + "ricardian_clauses": [], + "variants": [ + { + "name": "variant_int8_int16_int32_int64_uint8_uint16_uint32_uint64_float32_float64_string_INT8_VEC_INT16_VEC_INT32_VEC_INT64_VEC_UINT8_VEC_UINT16_VEC_UINT32_VEC_UINT64_VEC_FLOAT32_VEC_FLOAT64_VEC_STRING_VEC", + "types": ["int8","int16","int32","int64","uint8","uint16","uint32","uint64","float32","float64","string","INT8_VEC","INT16_VEC","INT32_VEC","INT64_VEC","UINT8_VEC","UINT16_VEC","UINT32_VEC","UINT64_VEC","FLOAT32_VEC","FLOAT64_VEC","STRING_VEC"] + } + ], + "action_results": [] +} \ No newline at end of file diff --git a/testdata/eosio.nft.ft.abi b/testdata/eosio.nft.ft.abi new file mode 100644 index 0000000..473f790 --- /dev/null +++ b/testdata/eosio.nft.ft.abi @@ -0,0 +1,707 @@ +{ + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", + "version": "eosio::abi/1.1", + "types": [ + { + "new_type_name": "asset_vector", + "type": "asset[]" + }, + { + "new_type_name": "issue_token_config_vector", + "type": "issue_token_config[]" + }, + { + "new_type_name": "name_vector", + "type": "name[]" + }, + { + "new_type_name": "resale_share_vector", + "type": "resale_share[]" + }, + { + "new_type_name": "string_vector", + "type": "string[]" + }, + { + "new_type_name": "time_since_mint", + "type": "uint32" + }, + { + "new_type_name": "uint64_t_vector", + "type": "uint64[]" + } + ], + "structs": [ + { + "name": "burn", + "base": "", + "fields": [ + { + "name": "burn", + "type": "burn_wrap" + } + ] + }, + { + "name": "burn_wrap", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name?" + }, + { + "name": "token_ids", + "type": "uint64_t_vector?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "buy", + "base": "", + "fields": [ + { + "name": "buy", + "type": "buy_wrap" + } + ] + }, + { + "name": "buy_wrap", + "base": "", + "fields": [ + { + "name": "buyer", + "type": "name?" + }, + { + "name": "receiver", + "type": "name?" + }, + { + "name": "token_id", + "type": "uint64?" + }, + { + "name": "max_price", + "type": "asset?" + }, + { + "name": "promoter_id", + "type": "name?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "cancelresell", + "base": "", + "fields": [ + { + "name": "cancelresell", + "type": "cancelresell_wrap" + } + ] + }, + { + "name": "cancelresell_wrap", + "base": "", + "fields": [ + { + "name": "token_id", + "type": "uint64?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "create", + "base": "", + "fields": [ + { + "name": "create", + "type": "create_wrap" + } + ] + }, + { + "name": "create_wrap", + "base": "", + "fields": [ + { + "name": "memo", + "type": "string?" + }, + { + "name": "version", + "type": "uint64?" + }, + { + "name": "asset_manager", + "type": "name?" + }, + { + "name": "asset_creator", + "type": "name?" + }, + { + "name": "conversion_rate_oracle_contract", + "type": "name?" + }, + { + "name": "chosen_rate", + "type": "asset_vector?" + }, + { + "name": "minimum_resell_price", + "type": "asset?" + }, + { + "name": "resale_shares", + "type": "resale_share_vector?" + }, + { + "name": "mintable_window_start", + "type": "time_point_sec?" + }, + { + "name": "mintable_window_end", + "type": "time_point_sec?" + }, + { + "name": "trading_window_start", + "type": "time_point_sec?" + }, + { + "name": "trading_window_end", + "type": "time_point_sec?" + }, + { + "name": "recall_window_start", + "type": "time_since_mint?" + }, + { + "name": "recall_window_end", + "type": "time_since_mint?" + }, + { + "name": "max_mintable_tokens", + "type": "uint32?" + }, + { + "name": "lockup_time", + "type": "uint32?" + }, + { + "name": "conditionless_receivers", + "type": "name_vector?" + }, + { + "name": "stat", + "type": "uint8?" + }, + { + "name": "meta_uris", + "type": "string_vector?" + }, + { + "name": "meta_hash", + "type": "checksum256?" + } + ] + }, + { + "name": "global_resale_share", + "base": "", + "fields": [ + { + "name": "receiver", + "type": "name" + }, + { + "name": "basis_point", + "type": "uint16" + } + ] + }, + { + "name": "globalshare", + "base": "", + "fields": [ + { + "name": "share", + "type": "uint16" + }, + { + "name": "receiver", + "type": "name?" + } + ] + }, + { + "name": "issue", + "base": "", + "fields": [ + { + "name": "issue", + "type": "issue_wrap" + } + ] + }, + { + "name": "issue_token_config", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "amount", + "type": "uint32" + }, + { + "name": "custom_data", + "type": "string" + } + ] + }, + { + "name": "issue_wrap", + "base": "", + "fields": [ + { + "name": "to", + "type": "name?" + }, + { + "name": "token_configs", + "type": "issue_token_config_vector?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "next_token_factory_number", + "base": "", + "fields": [ + { + "name": "value", + "type": "uint64" + } + ] + }, + { + "name": "next_token_number", + "base": "", + "fields": [ + { + "name": "value", + "type": "uint64" + } + ] + }, + { + "name": "recall", + "base": "", + "fields": [ + { + "name": "recall", + "type": "recall_wrap" + } + ] + }, + { + "name": "recall_wrap", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name?" + }, + { + "name": "token_ids", + "type": "uint64_t_vector?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "resale", + "base": "", + "fields": [ + { + "name": "token_id", + "type": "uint64" + }, + { + "name": "owner", + "type": "name" + }, + { + "name": "price", + "type": "asset" + }, + { + "name": "promoter_basis_point", + "type": "uint16" + } + ] + }, + { + "name": "resale_share", + "base": "", + "fields": [ + { + "name": "receiver", + "type": "name" + }, + { + "name": "basis_point", + "type": "uint16" + } + ] + }, + { + "name": "resell", + "base": "", + "fields": [ + { + "name": "resell", + "type": "resell_wrap" + } + ] + }, + { + "name": "resell_wrap", + "base": "", + "fields": [ + { + "name": "seller", + "type": "name?" + }, + { + "name": "token_id", + "type": "uint64?" + }, + { + "name": "price", + "type": "asset?" + }, + { + "name": "promoter_basis_point", + "type": "uint16?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "token", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "token_factory_id", + "type": "uint64" + }, + { + "name": "mint_date", + "type": "time_point_sec" + }, + { + "name": "serial_number", + "type": "uint32" + } + ] + }, + { + "name": "token_factory", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "asset_manager", + "type": "name" + }, + { + "name": "asset_creator", + "type": "name" + }, + { + "name": "conversion_rate_oracle_contract", + "type": "name" + }, + { + "name": "chosen_rate", + "type": "asset[]" + }, + { + "name": "minimum_resell_price", + "type": "asset" + }, + { + "name": "resale_shares", + "type": "resale_share[]" + }, + { + "name": "mintable_window_start", + "type": "uint32?" + }, + { + "name": "mintable_window_end", + "type": "uint32?" + }, + { + "name": "trading_window_start", + "type": "uint32?" + }, + { + "name": "trading_window_end", + "type": "uint32?" + }, + { + "name": "recall_window_start", + "type": "uint32?" + }, + { + "name": "recall_window_end", + "type": "uint32?" + }, + { + "name": "lockup_time", + "type": "uint32?" + }, + { + "name": "conditionless_receivers", + "type": "name[]" + }, + { + "name": "stat", + "type": "uint8" + }, + { + "name": "meta_uris", + "type": "string[]" + }, + { + "name": "meta_hash", + "type": "checksum256" + }, + { + "name": "max_mintable_tokens", + "type": "uint32?" + }, + { + "name": "minted_tokens_no", + "type": "uint32" + }, + { + "name": "existing_tokens_no", + "type": "uint32" + } + ] + }, + { + "name": "transfer", + "base": "", + "fields": [ + { + "name": "transfer", + "type": "transfer_wrap" + } + ] + }, + { + "name": "transfer_wrap", + "base": "", + "fields": [ + { + "name": "from", + "type": "name?" + }, + { + "name": "to", + "type": "name?" + }, + { + "name": "token_ids", + "type": "uint64_t_vector?" + }, + { + "name": "memo", + "type": "string?" + } + ] + }, + { + "name": "update", + "base": "", + "fields": [ + { + "name": "update", + "type": "update_wrap" + } + ] + }, + { + "name": "update_wrap", + "base": "", + "fields": [ + { + "name": "token_factory_id", + "type": "uint64?" + }, + { + "name": "memo", + "type": "string?" + }, + { + "name": "conditionless_receivers", + "type": "name_vector?" + }, + { + "name": "stat", + "type": "uint8?" + }, + { + "name": "meta_uris", + "type": "string_vector?" + }, + { + "name": "meta_hash", + "type": "checksum256?" + } + ] + } + ], + "actions": [ + { + "name": "burn", + "type": "burn", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Burn Tokens\nsummary: 'Erase the provided list of tokens from existence.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{owner}} will burn the following tokens {{ token_ids }} from existence. Reason: {{ memo }}." + }, + { + "name": "buy", + "type": "buy", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Buy Token\nsummary: 'Buy a single token listed for a sale'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{buyer}} will buy the token {{ token_id }} for a listed price and if specified a promoter {{promoter_id}} will participate in the sale share distribution. Reason: {{ memo }}." + }, + { + "name": "cancelresell", + "type": "cancelresell", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Cancel Token Resell\nsummary: 'Cancel an existing token resell opertaion'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nresell operation of the token {{token_id}} will be cancelled and it will no longer be listed for a sale. Reason: {{ memo }}." + }, + { + "name": "create", + "type": "create", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create Token Factory\nsummary: 'Create a new token factory.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_creator}} will create new token factory which will be managed by {{asset_manager}}. Reason: {{ memo }}." + }, + { + "name": "globalshare", + "type": "globalshare", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set global resale share\nsummary: 'Set or update global NFT token resale share'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\neach NFT token resale will additionally send a share of {{share}} basis points to the existing global share account or {{receiver}} (if specified)" + }, + { + "name": "issue", + "type": "issue", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Issue/Mint Tokens\nsummary: 'Mints a new token'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{to}} will receive the following tokens {{ token_configs }}. Memo: {{ memo }}." + }, + { + "name": "recall", + "type": "recall", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Recall Tokens\nsummary: 'Recall tokens from user to token factory manager.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will recall following tokens {{ token_ids }} from users. Reason: {{ memo }}." + }, + { + "name": "resell", + "type": "resell", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Resell Token\nsummary: 'List a single token for a sale'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{seller}} will list the token {{ token_id }} for a price of {{price}} and a promoter basis point of {{promoter_basis_point}} percent. Reason: {{ memo }}." + }, + { + "name": "transfer", + "type": "transfer", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Transfer Tokens\nsummary: 'Transfer multiple tokens between accounts'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nuser {{from}} will transfer the following tokens {{ token_ids }} to {{to}} account. Reason: {{ memo }}." + }, + { + "name": "update", + "type": "update", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Update Token Factory\nsummary: 'Update an existing token factory.'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{asset_manager}} will update certain configs for factory. Reason: {{ memo }}." + } + ], + "tables": [ + { + "name": "factory.a", + "type": "token_factory", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "factory.b", + "type": "token_factory", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "global.share", + "type": "global_resale_share", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "next.factory", + "type": "next_token_factory_number", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "next.token", + "type": "next_token_number", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "resale.a", + "type": "resale", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "token.a", + "type": "token", + "index_type": "i64", + "key_names": [], + "key_types": [] + } + ], + "ricardian_clauses": [], + "variants": [] +} \ No newline at end of file diff --git a/testdata/eosio.oracle.abi b/testdata/eosio.oracle.abi new file mode 100644 index 0000000..6c5d9e1 --- /dev/null +++ b/testdata/eosio.oracle.abi @@ -0,0 +1,403 @@ +{ + "version": "eosio::abi/1.1", + "structs": [ + { + "name": "addma", + "base": "", + "fields": [ + { + "name": "final_moving_average_settings", + "type": "asset[]" + } + ] + }, + { + "name": "calcsecma", + "base": "", + "fields": [ + { + "name": "moving_average_setting", + "type": "asset" + } + ] + }, + { + "name": "feed_completion_data", + "base": "", + "fields": [ + { + "name": "intervals", + "type": "feed_completion_element[]" + } + ] + }, + { + "name": "feed_completion_element", + "base": "", + "fields": [ + { + "name": "sources_pushed", + "type": "uint64" + }, + { + "name": "timestamp", + "type": "uint64" + } + ] + }, + { + "name": "feed_data", + "base": "", + "fields": [ + { + "name": "source", + "type": "name" + }, + { + "name": "rates", + "type": "uint64[]" + }, + { + "name": "weight", + "type": "uint64" + }, + { + "name": "source_type", + "type": "uint8" + } + ] + }, + { + "name": "final_rates_data", + "base": "", + "fields": [ + { + "name": "index", + "type": "uint64" + }, + { + "name": "rates", + "type": "urate[]" + }, + { + "name": "rolling_moving_average", + "type": "moving_average_impl" + } + ] + }, + { + "name": "init", + "base": "", + "fields": [ + { + "name": "interval", + "type": "uint8" + }, + { + "name": "cache_window", + "type": "uint32" + }, + { + "name": "final_price_table_size", + "type": "uint32[]" + }, + { + "name": "final_moving_average_settings", + "type": "asset[]" + }, + { + "name": "ultra_comprehensive_rate_weight", + "type": "uint32" + } + ] + }, + { + "name": "last_known_rate", + "base": "", + "fields": [ + { + "name": "source", + "type": "name" + }, + { + "name": "latest_rate", + "type": "rate" + } + ] + }, + { + "name": "moving_average_impl", + "base": "", + "fields": [ + { + "name": "average", + "type": "rate" + }, + { + "name": "is_valid_deprecated", + "type": "bool" + }, + { + "name": "param", + "type": "uint64" + }, + { + "name": "moving_window_counter", + "type": "uint8" + }, + { + "name": "unit", + "type": "uint8" + } + ] + }, + { + "name": "oracle_state_data", + "base": "", + "fields": [ + { + "name": "conversion_rate_symbol", + "type": "symbol" + }, + { + "name": "trading_volume_symbol", + "type": "symbol" + }, + { + "name": "latest_timestamp", + "type": "uint64" + }, + { + "name": "interval", + "type": "uint8" + }, + { + "name": "cache_window", + "type": "uint8" + }, + { + "name": "registered_sources", + "type": "uint32" + }, + { + "name": "ultra_comprehensive_rate_weight", + "type": "uint32" + } + ] + }, + { + "name": "purgefrates", + "base": "", + "fields": [ + { + "name": "scope", + "type": "uint64" + } + ] + }, + { + "name": "pushrate", + "base": "", + "fields": [ + { + "name": "exchange", + "type": "name" + }, + { + "name": "rates", + "type": "rate[]" + }, + { + "name": "volume", + "type": "asset" + } + ] + }, + { + "name": "rate", + "base": "", + "fields": [ + { + "name": "timestamp", + "type": "uint64" + }, + { + "name": "price", + "type": "asset" + } + ] + }, + { + "name": "regexchange", + "base": "", + "fields": [ + { + "name": "exchange", + "type": "name" + } + ] + }, + { + "name": "removema", + "base": "", + "fields": [ + { + "name": "final_moving_average_settings", + "type": "asset[]" + } + ] + }, + { + "name": "resetfavg", + "base": "", + "fields": [ + { + "name": "time_symbol", + "type": "symbol?" + } + ] + }, + { + "name": "resetfeed", + "base": "", + "fields": [ + { + "name": "exchange", + "type": "name?" + } + ] + }, + { + "name": "resetfrates", + "base": "", + "fields": [ + { + "name": "scope", + "type": "uint64?" + } + ] + }, + { + "name": "terminate", + "base": "" + }, + { + "name": "unregexchg", + "base": "", + "fields": [ + { + "name": "exchange", + "type": "name" + } + ] + }, + { + "name": "urate", + "base": "", + "fields": [ + { + "name": "timestamp", + "type": "uint64" + }, + { + "name": "price", + "type": "uint64" + } + ] + } + ], + "actions": [ + { + "name": "addma", + "type": "addma", + "ricardian_contract": "" + }, + { + "name": "calcsecma", + "type": "calcsecma", + "ricardian_contract": "" + }, + { + "name": "init", + "type": "init", + "ricardian_contract": "" + }, + { + "name": "purgefrates", + "type": "purgefrates", + "ricardian_contract": "" + }, + { + "name": "pushrate", + "type": "pushrate", + "ricardian_contract": "" + }, + { + "name": "regexchange", + "type": "regexchange", + "ricardian_contract": "" + }, + { + "name": "removema", + "type": "removema", + "ricardian_contract": "" + }, + { + "name": "resetfavg", + "type": "resetfavg", + "ricardian_contract": "" + }, + { + "name": "resetfeed", + "type": "resetfeed", + "ricardian_contract": "" + }, + { + "name": "resetfrates", + "type": "resetfrates", + "ricardian_contract": "" + }, + { + "name": "terminate", + "type": "terminate", + "ricardian_contract": "" + }, + { + "name": "unregexchg", + "type": "unregexchg", + "ricardian_contract": "" + } + ], + "tables": [ + { + "name": "feedcompl", + "index_type": "i64", + "type": "feed_completion_data" + }, + { + "name": "feeddata", + "index_type": "i64", + "type": "feed_data" + }, + { + "name": "finalaverage", + "index_type": "i64", + "type": "moving_average_impl" + }, + { + "name": "finalrates", + "index_type": "i64", + "type": "final_rates_data" + }, + { + "name": "lastknwnrate", + "index_type": "i64", + "type": "last_known_rate" + }, + { + "name": "oraclestate", + "index_type": "i64", + "type": "oracle_state_data" + } + ] +} \ No newline at end of file diff --git a/testdata/eosio.token-2.abi b/testdata/eosio.token-2.abi new file mode 100644 index 0000000..d7efc61 --- /dev/null +++ b/testdata/eosio.token-2.abi @@ -0,0 +1,354 @@ +{ + "version": "eosio::abi/1.2", + "structs": [ + { + "name": "account", + "base": "", + "fields": [ + { + "name": "balance", + "type": "asset" + } + ] + }, + { + "name": "burn", + "base": "", + "fields": [ + { + "name": "amount", + "type": "asset" + } + ] + }, + { + "name": "close", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "symbol", + "type": "symbol" + } + ] + }, + { + "name": "configburn", + "base": "", + "fields": [ + { + "name": "trigger_supply", + "type": "asset" + }, + { + "name": "rate_bp", + "type": "uint16" + }, + { + "name": "whitelisted_accounts", + "type": "name[]" + } + ] + }, + { + "name": "configtax", + "base": "", + "fields": [ + { + "name": "trigger_supply", + "type": "asset" + }, + { + "name": "rate_bp", + "type": "uint16" + }, + { + "name": "tax_receiver", + "type": "name" + }, + { + "name": "whitelisted_accounts", + "type": "name[]" + } + ] + }, + { + "name": "create", + "base": "", + "fields": [ + { + "name": "issuer", + "type": "name" + }, + { + "name": "maximum_supply", + "type": "asset" + } + ] + }, + { + "name": "currency_stats", + "base": "", + "fields": [ + { + "name": "supply", + "type": "asset" + }, + { + "name": "max_supply", + "type": "asset" + }, + { + "name": "issuer", + "type": "name" + } + ] + }, + { + "name": "issue", + "base": "", + "fields": [ + { + "name": "to", + "type": "name" + }, + { + "name": "quantity", + "type": "asset" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "metadata", + "base": "", + "fields": [ + { + "name": "symbol", + "type": "symbol" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "icon", + "type": "string" + }, + { + "name": "description", + "type": "string" + }, + { + "name": "color", + "type": "uint32" + } + ] + }, + { + "name": "open", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "symbol", + "type": "symbol" + }, + { + "name": "ram_payer", + "type": "name" + } + ] + }, + { + "name": "retire", + "base": "", + "fields": [ + { + "name": "quantity", + "type": "asset" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "tax", + "base": "", + "fields": [ + { + "name": "amount", + "type": "asset" + }, + { + "name": "tax_receiver", + "type": "name" + } + ] + }, + { + "name": "token_v1", + "base": "", + "fields": [ + { + "name": "trigger_supply", + "type": "asset" + }, + { + "name": "strategy", + "type": "uint16" + }, + { + "name": "rate_bp", + "type": "uint16" + }, + { + "name": "tax_receiver", + "type": "name?" + }, + { + "name": "whitelisted_accounts", + "type": "name[]" + } + ] + }, + { + "name": "transfer", + "base": "", + "fields": [ + { + "name": "from", + "type": "name" + }, + { + "name": "to", + "type": "name" + }, + { + "name": "quantity", + "type": "asset" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "updatemeta", + "base": "", + "fields": [ + { + "name": "symbol", + "type": "symbol" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "icon", + "type": "string" + }, + { + "name": "description", + "type": "string" + }, + { + "name": "color", + "type": "uint32" + } + ] + } + ], + "actions": [ + { + "name": "burn", + "type": "burn", + "ricardian_contract": "" + }, + { + "name": "close", + "type": "close", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Close Token Balance\nsummary: 'Close {{nowrap owner}}’s zero quantity balance'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/token.png#207ff68b0406eaa56618b08bda81d6a0954543f36adc328ab3065f31a5c5d654\n---\n\n{{owner}} agrees to close their zero quantity balance for the {{symbol_to_symbol_code symbol}} token.\n\nRAM will be refunded to the RAM payer of the {{symbol_to_symbol_code symbol}} token balance for {{owner}}." + }, + { + "name": "configburn", + "type": "configburn", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Update Tokens Burn Policy\nsummary: 'Update Token Burn Policy'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/transfer.png#5dfad0df72772ee1ccc155e670c1d124f5c5122f1d5027565df38b418042d1dd\n---\n\nThe token {{ symbol }} creator or Ultra can update token burn policy which will apply during transfer if conditions are met." + }, + { + "name": "configtax", + "type": "configtax", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Update Tokens Tax Policy\nsummary: 'Update Token Tax Policy'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/transfer.png#5dfad0df72772ee1ccc155e670c1d124f5c5122f1d5027565df38b418042d1dd\n---\n\nThe token {{ symbol }} creator or Ultra can update token Tax policy which will apply during transfer if conditions are met." + }, + { + "name": "create", + "type": "create", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create New Token\nsummary: 'Create a new token'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/token.png#207ff68b0406eaa56618b08bda81d6a0954543f36adc328ab3065f31a5c5d654\n---\n\n{{$action.account}} agrees to create a new token with symbol {{asset_to_symbol_code maximum_supply}} to be managed by {{issuer}}.\n\nThis action will not result any any tokens being issued into circulation.\n\n{{issuer}} will be allowed to issue tokens into circulation, up to a maximum supply of {{maximum_supply}}.\n\nRAM will deducted from {{$action.account}}’s resources to create the necessary records." + }, + { + "name": "issue", + "type": "issue", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Issue Tokens into Circulation\nsummary: 'Issue {{nowrap quantity}} into circulation and transfer into {{nowrap to}}’s account'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/token.png#207ff68b0406eaa56618b08bda81d6a0954543f36adc328ab3065f31a5c5d654\n---\n\nThe token manager agrees to issue {{quantity}} into circulation, and transfer it into {{to}}’s account.\n\n{{#if memo}}There is a memo attached to the transfer stating:\n{{memo}}\n{{/if}}\n\nIf {{to}} does not have a balance for {{asset_to_symbol_code quantity}}, or the token manager does not have a balance for {{asset_to_symbol_code quantity}}, the token manager will be designated as the RAM payer of the {{asset_to_symbol_code quantity}} token balance for {{to}}. As a result, RAM will be deducted from the token manager’s resources to create the necessary records.\n\nThis action does not allow the total quantity to exceed the max allowed supply of the token." + }, + { + "name": "open", + "type": "open", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Open Token Balance\nsummary: 'Open a zero quantity balance for {{nowrap owner}}'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/token.png#207ff68b0406eaa56618b08bda81d6a0954543f36adc328ab3065f31a5c5d654\n---\n\n{{ram_payer}} agrees to establish a zero quantity balance for {{owner}} for the {{symbol_to_symbol_code symbol}} token.\n\nIf {{owner}} does not have a balance for {{symbol_to_symbol_code symbol}}, {{ram_payer}} will be designated as the RAM payer of the {{symbol_to_symbol_code symbol}} token balance for {{owner}}. As a result, RAM will be deducted from {{ram_payer}}’s resources to create the necessary records." + }, + { + "name": "retire", + "type": "retire", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Remove Tokens from Circulation\nsummary: 'Remove {{nowrap quantity}} from circulation'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/token.png#207ff68b0406eaa56618b08bda81d6a0954543f36adc328ab3065f31a5c5d654\n---\n\nThe token manager agrees to remove {{quantity}} from circulation, taken from their own account.\n\n{{#if memo}} There is a memo attached to the action stating:\n{{memo}}\n{{/if}}" + }, + { + "name": "tax", + "type": "tax", + "ricardian_contract": "" + }, + { + "name": "transfer", + "type": "transfer", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Transfer Tokens\nsummary: 'Send {{nowrap quantity}} from {{nowrap from}} to {{nowrap to}}'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/transfer.png#5dfad0df72772ee1ccc155e670c1d124f5c5122f1d5027565df38b418042d1dd\n---\n\n{{from}} agrees to send {{quantity}} to {{to}}.\n\n{{#if memo}}There is a memo attached to the transfer stating:\n{{memo}}\n{{/if}}\n\nIf {{from}} is not already the RAM payer of their {{asset_to_symbol_code quantity}} token balance, {{from}} will be designated as such. As a result, RAM will be deducted from {{from}}’s resources to refund the original RAM payer.\n\nIf {{to}} does not have a balance for {{asset_to_symbol_code quantity}}, {{from}} will be designated as the RAM payer of the {{asset_to_symbol_code quantity}} token balance for {{to}}. As a result, RAM will be deducted from {{from}}’s resources to create the necessary records." + }, + { + "name": "updatemeta", + "type": "updatemeta", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Update Tokens metadata\nsummary: 'Update {{nowrap symbol}} metadata'\nicon: https://raw.githubusercontent.com/AntelopeIO/reference-contracts/main/contracts/icons/transfer.png#5dfad0df72772ee1ccc155e670c1d124f5c5122f1d5027565df38b418042d1dd\n---\n\nThe token {{ symbol }} creator can add metadata including {{ name }}, {{ icon }}, {{ description }} and {{ color }}" + } + ], + "tables": [ + { + "name": "accounts", + "index_type": "i64", + "type": "account" + }, + { + "name": "metadata", + "index_type": "i64", + "type": "metadata" + }, + { + "name": "stat", + "index_type": "i64", + "type": "currency_stats" + }, + { + "name": "tokenconfig", + "index_type": "i64", + "type": "token_v1" + } + ] +} \ No newline at end of file diff --git a/testdata/eosio.token.abi b/testdata/eosio.token.abi new file mode 100644 index 0000000..085a731 --- /dev/null +++ b/testdata/eosio.token.abi @@ -0,0 +1,177 @@ +{ + "version": "eosio::abi/1.1", + "structs": [ + { + "name": "account", + "base": "", + "fields": [ + { + "name": "balance", + "type": "asset" + } + ] + }, + { + "name": "close", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "symbol", + "type": "symbol" + } + ] + }, + { + "name": "create", + "base": "", + "fields": [ + { + "name": "issuer", + "type": "name" + }, + { + "name": "maximum_supply", + "type": "asset" + } + ] + }, + { + "name": "currency_stats", + "base": "", + "fields": [ + { + "name": "supply", + "type": "asset" + }, + { + "name": "max_supply", + "type": "asset" + }, + { + "name": "issuer", + "type": "name" + } + ] + }, + { + "name": "issue", + "base": "", + "fields": [ + { + "name": "to", + "type": "name" + }, + { + "name": "quantity", + "type": "asset" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "open", + "base": "", + "fields": [ + { + "name": "owner", + "type": "name" + }, + { + "name": "symbol", + "type": "symbol" + }, + { + "name": "ram_payer", + "type": "name" + } + ] + }, + { + "name": "retire", + "base": "", + "fields": [ + { + "name": "quantity", + "type": "asset" + }, + { + "name": "memo", + "type": "string" + } + ] + }, + { + "name": "transfer", + "base": "", + "fields": [ + { + "name": "from", + "type": "name" + }, + { + "name": "to", + "type": "name" + }, + { + "name": "quantity", + "type": "asset" + }, + { + "name": "memo", + "type": "string" + } + ] + } + ], + "actions": [ + { + "name": "close", + "type": "close", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Close Token Balance\nsummary: 'Close {{nowrap owner}}’s zero quantity balance'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/token.png#207ff68b0406eaa56618b08bda81d6a0954543f36adc328ab3065f31a5c5d654\n---\n\n{{owner}} agrees to close their zero quantity balance for the {{symbol_to_symbol_code symbol}} token.\n\nRAM will be refunded to the RAM payer of the {{symbol_to_symbol_code symbol}} token balance for {{owner}}." + }, + { + "name": "create", + "type": "create", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create New Token\nsummary: 'Create a new token'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/token.png#207ff68b0406eaa56618b08bda81d6a0954543f36adc328ab3065f31a5c5d654\n---\n\n{{$action.account}} agrees to create a new token with symbol {{asset_to_symbol_code maximum_supply}} to be managed by {{issuer}}.\n\nThis action will not result any any tokens being issued into circulation.\n\n{{issuer}} will be allowed to issue tokens into circulation, up to a maximum supply of {{maximum_supply}}.\n\nRAM will deducted from {{$action.account}}’s resources to create the necessary records." + }, + { + "name": "issue", + "type": "issue", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Issue Tokens into Circulation\nsummary: 'Issue {{nowrap quantity}} into circulation and transfer into {{nowrap to}}’s account'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/token.png#207ff68b0406eaa56618b08bda81d6a0954543f36adc328ab3065f31a5c5d654\n---\n\nThe token manager agrees to issue {{quantity}} into circulation, and transfer it into {{to}}’s account.\n\n{{#if memo}}There is a memo attached to the transfer stating:\n{{memo}}\n{{/if}}\n\nIf {{to}} does not have a balance for {{asset_to_symbol_code quantity}}, or the token manager does not have a balance for {{asset_to_symbol_code quantity}}, the token manager will be designated as the RAM payer of the {{asset_to_symbol_code quantity}} token balance for {{to}}. As a result, RAM will be deducted from the token manager’s resources to create the necessary records.\n\nThis action does not allow the total quantity to exceed the max allowed supply of the token." + }, + { + "name": "open", + "type": "open", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Open Token Balance\nsummary: 'Open a zero quantity balance for {{nowrap owner}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/token.png#207ff68b0406eaa56618b08bda81d6a0954543f36adc328ab3065f31a5c5d654\n---\n\n{{ram_payer}} agrees to establish a zero quantity balance for {{owner}} for the {{symbol_to_symbol_code symbol}} token.\n\nIf {{owner}} does not have a balance for {{symbol_to_symbol_code symbol}}, {{ram_payer}} will be designated as the RAM payer of the {{symbol_to_symbol_code symbol}} token balance for {{owner}}. As a result, RAM will be deducted from {{ram_payer}}’s resources to create the necessary records." + }, + { + "name": "retire", + "type": "retire", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Remove Tokens from Circulation\nsummary: 'Remove {{nowrap quantity}} from circulation'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/token.png#207ff68b0406eaa56618b08bda81d6a0954543f36adc328ab3065f31a5c5d654\n---\n\nThe token manager agrees to remove {{quantity}} from circulation, taken from their own account.\n\n{{#if memo}} There is a memo attached to the action stating:\n{{memo}}\n{{/if}}" + }, + { + "name": "transfer", + "type": "transfer", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Transfer Tokens\nsummary: 'Send {{nowrap quantity}} from {{nowrap from}} to {{nowrap to}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/transfer.png#5dfad0df72772ee1ccc155e670c1d124f5c5122f1d5027565df38b418042d1dd\n---\n\n{{from}} agrees to send {{quantity}} to {{to}}.\n\n{{#if memo}}There is a memo attached to the transfer stating:\n{{memo}}\n{{/if}}\n\nIf {{from}} is not already the RAM payer of their {{asset_to_symbol_code quantity}} token balance, {{from}} will be designated as such. As a result, RAM will be deducted from {{from}}’s resources to refund the original RAM payer.\n\nIf {{to}} does not have a balance for {{asset_to_symbol_code quantity}}, {{from}} will be designated as the RAM payer of the {{asset_to_symbol_code quantity}} token balance for {{to}}. As a result, RAM will be deducted from {{from}}’s resources to create the necessary records." + } + ], + "tables": [ + { + "name": "accounts", + "index_type": "i64", + "type": "account" + }, + { + "name": "stat", + "index_type": "i64", + "type": "currency_stats" + } + ] +} \ No newline at end of file diff --git a/testdata/ultra.rgrab.abi b/testdata/ultra.rgrab.abi new file mode 100644 index 0000000..a69ac8c --- /dev/null +++ b/testdata/ultra.rgrab.abi @@ -0,0 +1,139 @@ +{ + "version": "eosio::abi/1.2", + "structs": [ + { + "name": "campaign", + "base": "", + "fields": [ + { + "name": "name", + "type": "name" + }, + { + "name": "quantity", + "type": "asset" + }, + { + "name": "points", + "type": "uint64" + }, + { + "name": "manager", + "type": "name" + }, + { + "name": "deadline", + "type": "uint32" + } + ] + }, + { + "name": "claimrewards", + "base": "", + "fields": [ + { + "name": "campaign", + "type": "name" + }, + { + "name": "wallet_id", + "type": "bytes" + } + ] + }, + { + "name": "closecampgn", + "base": "", + "fields": [ + { + "name": "campaign", + "type": "name" + }, + { + "name": "manager", + "type": "name" + }, + { + "name": "limit", + "type": "uint32" + } + ] + }, + { + "name": "createcampgn", + "base": "", + "fields": [ + { + "name": "campaign", + "type": "name" + }, + { + "name": "manager", + "type": "name" + } + ] + }, + { + "name": "whitelist", + "base": "", + "fields": [ + { + "name": "wallet_id", + "type": "bytes" + }, + { + "name": "points", + "type": "uint64" + } + ] + }, + { + "name": "whitelistusr", + "base": "", + "fields": [ + { + "name": "campaign", + "type": "name" + }, + { + "name": "wallets", + "type": "whitelist[]" + } + ] + } + ], + "actions": [ + { + "name": "claimrewards", + "type": "claimrewards", + "ricardian_contract": "" + }, + { + "name": "closecampgn", + "type": "closecampgn", + "ricardian_contract": "" + }, + { + "name": "createcampgn", + "type": "createcampgn", + "ricardian_contract": "" + }, + { + "name": "whitelistusr", + "type": "whitelistusr", + "ricardian_contract": "" + } + ], + "tables": [ + { + "name": "campaign", + "index_type": "i64", + "type": "campaign" + }, + { + "name": "whitelist", + "index_type": "i64", + "type": "whitelist" + } + ] +} \ No newline at end of file diff --git a/transaction.go b/transaction.go new file mode 100644 index 0000000..fe359cc --- /dev/null +++ b/transaction.go @@ -0,0 +1,355 @@ +package dkafka + +import ( + "fmt" + + "github.com/confluentinc/confluent-kafka-go/kafka" + pbcodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/codec/v1" +) + +const transactionNotification = "TransactionNotification" +const transactionNamespace = "io.ultra.dkafka.transaction" + +type transactionMetaSupplier struct { +} + +func (dms transactionMetaSupplier) GetVersion() string { + return "1.0.0" +} +func (dms transactionMetaSupplier) GetSource() string { + return "dkafka-cli" +} +func (dms transactionMetaSupplier) GetDomain() string { + return "dkafka" +} +func (dms transactionMetaSupplier) GetCompatibility() string { + return "FORWARD" +} +func (dms transactionMetaSupplier) GetType() string { + return "notification" +} + +var TransactionMessageSchema = MessageSchema{ + transactionSchema, + newMeta(transactionMetaSupplier{}), +} + +type TransactionReceiptHeaderBasicSchema = RecordSchema +type ActionTraceBasicSchema = RecordSchema +type ActionBasicSchema = RecordSchema +type PermissionLevelBasicSchema = RecordSchema +type AccountRamDeltasBasicSchema = RecordSchema +type ExceptionBasicSchema = RecordSchema +type RAMOpBasicSchema = RecordSchema +type CreationTreeBasicSchema = RecordSchema + +func Map[T, U any](ts []T, f func(T) U) []U { + us := make([]U, len(ts)) + for i := range ts { + us[i] = f(ts[i]) + } + return us +} + +func newTransactionReceiptHeaderBasicSchema() TransactionReceiptHeaderBasicSchema { + return newRecordS( + "TransactionReceiptHeader", + []FieldSchema{ + NewOptionalField("status", "int"), + NewOptionalField("cpu_usage_micro_seconds", "long"), + }, + ) +} + +func newActionTraceBasicSchema() ActionTraceBasicSchema { + return newRecordS( + "ActionTrace", + []FieldSchema{ + NewOptionalField("receiver", "string"), + NewOptionalField("action", newActionBasicSchema()), + NewOptionalField("context_free", "boolean"), + NewOptionalField("elapsed", "long"), + {Name: "account_ram_deltas", Type: NewArray(newAccountRamDeltasBasicSchema())}, + NewOptionalField("exception", newExceptionBasicSchema()), + NewOptionalField("error_code", NewUint64Type()), + NewOptionalField("action_ordinal", "int"), + NewOptionalField("creator_action_ordinal", "int"), + NewOptionalField("closest_unnotified_ancestor_action_ordinal", "int"), + NewOptionalField("execution_index", "int"), + }, + ) +} + +func newActionBasicSchema() ActionBasicSchema { + return newRecordS( + "Action", + []FieldSchema{ + NewOptionalField("account", "string"), + NewOptionalField("name", "string"), + {Name: "authorization", Type: NewArray(newPermissionLevelBasicSchema())}, + }, + ) +} +func newPermissionLevelBasicSchema() PermissionLevelBasicSchema { + return newRecordS( + "PermissionLevel", + []FieldSchema{ + NewOptionalField("actor", "string"), + NewOptionalField("permission", "string"), + }, + ) +} + +func newAccountRamDeltasBasicSchema() AccountRamDeltasBasicSchema { + return newRecordS( + "AccountRamDelta", + []FieldSchema{ + NewOptionalField("account", "string"), + NewOptionalField("delta", "long"), + }, + ) +} + +func newExceptionBasicSchema() ExceptionBasicSchema { + return newRecordS( + "Exception", + []FieldSchema{ + NewOptionalField("code", "int"), + NewOptionalField("name", "string"), + NewOptionalField("message", "string"), + }, + ) +} + +func newRAMOpBasicSchema() RAMOpBasicSchema { + return newRecordS( + "RAMOp", + []FieldSchema{ + NewOptionalField("operation", "int"), + NewOptionalField("action_index", "long"), + NewOptionalField("payer", "string"), + NewOptionalField("delta", "long"), + NewOptionalField("usage", "long"), + }, + ) +} + +func newCreationTreeBasicSchema() CreationTreeBasicSchema { + return newRecordS( + "CreationFlatNode", + []FieldSchema{ + NewOptionalField("creator_action_index", "long"), + NewOptionalField("execution_action_index", "long"), + }, + ) +} + +var transactionSchema = RecordSchema{ + Type: "record", + Name: transactionNotification, + Namespace: transactionNamespace, + Doc: "Transaction info", + Fields: []FieldSchema{ + NewOptionalField("id", "string"), + NewOptionalField("block_num", "long"), + NewOptionalField("index", "long"), + NewOptionalField("block_time", NewTimestampMillisType("block_timestamp_type")), + NewOptionalField("producer_block_id", "string"), + NewOptionalField("receipt", newTransactionReceiptHeaderBasicSchema()), + NewOptionalField("elapsed", "long"), + NewOptionalField("net_usage", "long"), + {Name: "action_traces", Type: NewArray(newActionTraceBasicSchema())}, + NewOptionalField("exception", "Exception"), + NewOptionalField("error_code", NewUint64Type()), + {Name: "ram_ops", Type: NewArray(newRAMOpBasicSchema())}, + {Name: "creation_tree", Type: NewArray(newCreationTreeBasicSchema())}, + }, +} + +func newTransactionMap(transaction *pbcodec.TransactionTrace) map[string]interface{} { + return map[string]interface{}{ + "id": transaction.Id, + "block_num": transaction.BlockNum, + "index": transaction.Index, + "block_time": transaction.BlockTime.AsTime(), + "producer_block_id": transaction.ProducerBlockId, + "receipt": newTransactionReceiptHeaderMap(transaction.Receipt), + "elapsed": transaction.Elapsed, + "net_usage": transaction.NetUsage, + "action_traces": newActionTracesMap(transaction.ActionTraces), + "exception": newExceptionMap(transaction.Exception), + "error_code": transaction.ErrorCode, + "ram_ops": newRAMOpsMap(transaction.RamOps), + "creation_tree": newCreationTreeMap(transaction.CreationTree), + } +} + +func newTransactionReceiptHeaderMap(transactionReceiptHeader *pbcodec.TransactionReceiptHeader) map[string]interface{} { + if transactionReceiptHeader != nil { + return map[string]interface{}{ + "status": transactionReceiptHeader.Status, + "cpu_usage_micro_seconds": transactionReceiptHeader.CpuUsageMicroSeconds, + } + } else { + return nil + } +} + +func newActionTracesMap(actionTraces []*pbcodec.ActionTrace) []map[string]interface{} { + return Map(actionTraces, newActionTraceMap) +} + +func newActionTraceMap(actionTrace *pbcodec.ActionTrace) map[string]interface{} { + if actionTrace != nil { + return map[string]interface{}{ + "receiver": actionTrace.Receiver, + "action": newActionMap(actionTrace.Action), + "context_free": actionTrace.ContextFree, + "elapsed": actionTrace.Elapsed, + "account_ram_deltas": newAccountRamDeltasMap(actionTrace.AccountRamDeltas), + "exception": newExceptionMap(actionTrace.Exception), + "error_code": actionTrace.ErrorCode, + "action_ordinal": actionTrace.ActionOrdinal, + "creator_action_ordinal": actionTrace.CreatorActionOrdinal, + "closest_unnotified_ancestor_action_ordinal": actionTrace.ClosestUnnotifiedAncestorActionOrdinal, + "execution_index": actionTrace.ExecutionIndex, + } + } else { + return nil + } +} + +func newActionMap(action *pbcodec.Action) map[string]interface{} { + if action != nil { + return map[string]interface{}{ + "account": action.Account, + "name": action.Name, + "authorization": newAuthorizationsMap(action.Authorization), + } + } else { + return nil + } +} + +func newAuthorizationsMap(exceptions []*pbcodec.PermissionLevel) []map[string]interface{} { + return Map(exceptions, newAuthorizationMap) +} + +func newAuthorizationMap(exception *pbcodec.PermissionLevel) map[string]interface{} { + if exception != nil { + return map[string]interface{}{ + "actor": exception.Actor, + "permission": exception.Permission, + } + } else { + return nil + } +} + +func newAccountRamDeltasMap(accountRAMDeltas []*pbcodec.AccountRAMDelta) []map[string]interface{} { + return Map(accountRAMDeltas, newAccountRamDeltaMap) +} + +func newAccountRamDeltaMap(accountRAMDelta *pbcodec.AccountRAMDelta) map[string]interface{} { + if accountRAMDelta != nil { + return map[string]interface{}{ + "account": accountRAMDelta.Account, + "delta": accountRAMDelta.Delta, + } + } else { + return nil + } +} + +func newExceptionMap(exception *pbcodec.Exception) map[string]interface{} { + if exception != nil { + return map[string]interface{}{ + "code": exception.Code, + "name": exception.Name, + "message": exception.Message, + } + } else { + return nil + } +} + +func newRAMOpsMap(rampOp []*pbcodec.RAMOp) []map[string]interface{} { + return Map(rampOp, newRAMOpMap) +} + +func newRAMOpMap(rampOp *pbcodec.RAMOp) map[string]interface{} { + if rampOp != nil { + return map[string]interface{}{ + "operation": rampOp.Operation, + "action_index": rampOp.ActionIndex, + "payer": rampOp.Payer, + "delta": rampOp.Delta, + "usage": rampOp.Usage, + } + } else { + return nil + } +} + +func newCreationTreeMap(creationFlatNodes []*pbcodec.CreationFlatNode) []map[string]interface{} { + return Map(creationFlatNodes, newCreationNodeMap) +} + +func newCreationNodeMap(creationFlatNode *pbcodec.CreationFlatNode) map[string]interface{} { + if creationFlatNode != nil { + return map[string]interface{}{ + "creator_action_index": creationFlatNode.CreatorActionIndex, + "execution_action_index": creationFlatNode.ExecutionActionIndex, + } + } else { + return nil + } +} + +type transactionGenerator struct { + headers []kafka.Header + topic string + abiCodec ABICodec +} + +func (t transactionGenerator) Apply(genContext TransactionContext) ([]*kafka.Message, error) { + transactionMap := newTransactionMap(genContext.transaction) + codec, err := t.abiCodec.GetCodec(transactionSchema.AsCodecId(), 0) + if err != nil { + return nil, fmt.Errorf("transactionGenerator.Apply() fail to get codec for %s: %w", transactionNotification, err) + } + value, err := codec.Marshal(nil, transactionMap) + if err != nil { + return nil, fmt.Errorf("transactionGenerator.Apply() fail to marshal %s: %w", transactionNotification, err) + } + transactionIdBytes := []byte(genContext.transaction.Id) + headers := append(t.headers, + kafka.Header{ + Key: "ce_id", + Value: transactionIdBytes, + }, + kafka.Header{ + Key: "ce_type", + Value: []byte(transactionNotification), + }, + kafka.Header{ + Key: "ce_blkstep", + Value: []byte(genContext.stepName), + }, + genContext.blockStep.timeHeader(), + newCursorHeader(genContext.cursor), + newPreviousCursorHeader(genContext.blockStep.previousCursor), + ) + headers = append(headers, codec.GetHeaders()...) + + msg := &kafka.Message{ + Key: transactionIdBytes, + Headers: headers, + Value: value, + TopicPartition: kafka.TopicPartition{ + Topic: &t.topic, + Partition: kafka.PartitionAny, + }, + } + return []*kafka.Message{msg}, nil +} diff --git a/transaction_test.go b/transaction_test.go new file mode 100644 index 0000000..ada418d --- /dev/null +++ b/transaction_test.go @@ -0,0 +1,363 @@ +package dkafka + +import ( + "math/big" + "reflect" + "testing" + "time" + + "github.com/confluentinc/confluent-kafka-go/kafka" + pbcodec "github.com/dfuse-io/dfuse-eosio/pb/dfuse/eosio/codec/v1" + "github.com/golang/protobuf/jsonpb" + "github.com/riferrei/srclient" + pbbstream "github.com/streamingfast/pbgo/dfuse/bstream/v1" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func Test_transactionGenerator_Apply(t *testing.T) { + timestamp := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC) + + blockStep := BlockStep{ + blk: &pbcodec.Block{ + Header: &pbcodec.BlockHeader{ + Timestamp: timestamppb.New(timestamp), + }, + }, + step: pbbstream.ForkStep_STEP_NEW, + cursor: "123", + } + + type fields struct { + headers []kafka.Header + topic string + } + + type expectedValues struct { + key string + ceId string + value interface{} + } + + tests := []struct { + name string + fields fields + args TransactionContext + expect expectedValues + wantErr bool + }{ + { + name: "default", + fields: fields{topic: "dkafka.test", headers: default_headers}, + args: TransactionContext{ + stepName: "", + transaction: &pbcodec.TransactionTrace{ + Id: "trx-1", + BlockNum: 0, + Index: 0, + BlockTime: timestamppb.New(timestamp), + ProducerBlockId: "", + Receipt: &pbcodec.TransactionReceiptHeader{ + Status: 0, + CpuUsageMicroSeconds: 0, + NetUsageWords: 0, + }, + Elapsed: 0, + NetUsage: 0, + Scheduled: false, + ActionTraces: []*pbcodec.ActionTrace{{ + Receiver: "", + Action: &pbcodec.Action{ + Account: "", + Name: "", + Authorization: []*pbcodec.PermissionLevel{{ + Actor: "", + Permission: "", + }}, + }, + ContextFree: false, + Elapsed: 0, + Console: "", + TransactionId: "", + BlockNum: 0, + ProducerBlockId: "", + BlockTime: timestamppb.New(timestamp), + AccountRamDeltas: []*pbcodec.AccountRAMDelta{{ + Account: "", + Delta: 0, + }}, + Exception: &pbcodec.Exception{ + Code: 0, + Name: "", + Message: "", + }, + ErrorCode: 0, + ActionOrdinal: 0, + CreatorActionOrdinal: 0, + ClosestUnnotifiedAncestorActionOrdinal: 0, + ExecutionIndex: 0, + }}, + FailedDtrxTrace: nil, + Exception: &pbcodec.Exception{ + Code: 0, + Name: "", + Message: "", + }, + ErrorCode: 0, + DbOps: []*pbcodec.DBOp{}, + DtrxOps: []*pbcodec.DTrxOp{}, + FeatureOps: []*pbcodec.FeatureOp{}, + PermOps: []*pbcodec.PermOp{}, + RamOps: []*pbcodec.RAMOp{{ + Operation: 0, + ActionIndex: 0, + Payer: "", + Delta: 0, + Usage: 0, + }}, + RamCorrectionOps: []*pbcodec.RAMCorrectionOp{}, + RlimitOps: []*pbcodec.RlimitOp{}, + TableOps: []*pbcodec.TableOp{}, + CreationTree: []*pbcodec.CreationFlatNode{{ + CreatorActionIndex: 0, + ExecutionActionIndex: 0, + }}, + }, + blockStep: blockStep, + cursor: "", + step: 0, + }, + expect: expectedValues{ + key: "trx-1", + ceId: "trx-1", + value: map[string]interface{}{ + "id": map[string]interface{}{"string": "trx-1"}, + "block_num": map[string]interface{}{"long": int64(0)}, + "index": map[string]interface{}{"long": int64(0)}, + "block_time": map[string]interface{}{"long.timestamp-millis": timestamp}, + "producer_block_id": map[string]interface{}{"string": ""}, + "receipt": map[string]interface{}{ + "io.ultra.dkafka.transaction.TransactionReceiptHeader": map[string]interface{}{ + "cpu_usage_micro_seconds": map[string]interface{}{"long": int64(0)}, + "status": map[string]interface{}{"int": int32(0)}, + }}, + "elapsed": map[string]interface{}{"long": int64(0)}, + "net_usage": map[string]interface{}{"long": int64(0)}, + "action_traces": []interface{}{ + map[string]interface{}{ + "receiver": map[string]interface{}{"string": ""}, + "action": map[string]interface{}{"io.ultra.dkafka.transaction.Action": map[string]interface{}{ + "account": map[string]interface{}{"string": ""}, + "name": map[string]interface{}{"string": ""}, + "authorization": []interface{}{ + map[string]interface{}{ + "actor": map[string]interface{}{"string": ""}, + "permission": map[string]interface{}{"string": ""}, + }, + }, + }}, + "context_free": map[string]interface{}{"boolean": false}, + "elapsed": map[string]interface{}{"long": int64(0)}, + "account_ram_deltas": []interface{}{ + map[string]interface{}{ + "account": map[string]interface{}{"string": ""}, + "delta": map[string]interface{}{"long": int64(0)}, + }, + }, + "exception": map[string]interface{}{ + "io.ultra.dkafka.transaction.Exception": map[string]interface{}{ + "code": map[string]interface{}{"int": int32(0)}, + "message": map[string]interface{}{"string": ""}, + "name": map[string]interface{}{"string": ""}, + }}, + "error_code": map[string]interface{}{"bytes.decimal": new(big.Rat).SetInt64(0)}, + "action_ordinal": map[string]interface{}{"int": int32(0)}, + "creator_action_ordinal": map[string]interface{}{"int": int32(0)}, + "closest_unnotified_ancestor_action_ordinal": map[string]interface{}{"int": int32(0)}, + "execution_index": map[string]interface{}{"int": int32(0)}, + }, + }, + "exception": map[string]interface{}{ + "io.ultra.dkafka.transaction.Exception": map[string]interface{}{ + "code": map[string]interface{}{"int": int32(0)}, + "message": map[string]interface{}{"string": ""}, + "name": map[string]interface{}{"string": ""}, + }}, + "error_code": map[string]interface{}{"bytes.decimal": new(big.Rat).SetInt64(0)}, + "ram_ops": []interface{}{ + map[string]interface{}{ + "operation": map[string]interface{}{"int": int32(0)}, + "action_index": map[string]interface{}{"long": int64(0)}, + "payer": map[string]interface{}{"string": ""}, + "delta": map[string]interface{}{"long": int64(0)}, + "usage": map[string]interface{}{"long": int64(0)}, + }, + }, + "creation_tree": []interface{}{ + map[string]interface{}{ + "creator_action_index": map[string]interface{}{"long": int64(0)}, + "execution_action_index": map[string]interface{}{"long": int64(0)}, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "empty_arrays_object", + fields: fields{topic: "dkafka.test", headers: default_headers}, + args: TransactionContext{ + stepName: "", + transaction: &pbcodec.TransactionTrace{ + Id: "trx-1", + BlockNum: 0, + Index: 0, + BlockTime: timestamppb.New(timestamp), + ProducerBlockId: "", + Receipt: nil, + Elapsed: 0, + NetUsage: 0, + Scheduled: false, + ActionTraces: []*pbcodec.ActionTrace{}, + FailedDtrxTrace: nil, + Exception: nil, + ErrorCode: 0, + DbOps: []*pbcodec.DBOp{}, + DtrxOps: []*pbcodec.DTrxOp{}, + FeatureOps: []*pbcodec.FeatureOp{}, + PermOps: []*pbcodec.PermOp{}, + RamOps: []*pbcodec.RAMOp{}, + RamCorrectionOps: []*pbcodec.RAMCorrectionOp{}, + RlimitOps: []*pbcodec.RlimitOp{}, + TableOps: []*pbcodec.TableOp{}, + CreationTree: []*pbcodec.CreationFlatNode{}, + }, + blockStep: blockStep, + cursor: "", + step: 0, + }, + expect: expectedValues{ + key: "trx-1", + ceId: "trx-1", + value: map[string]interface{}{ + "id": map[string]interface{}{"string": "trx-1"}, + "block_num": map[string]interface{}{"long": int64(0)}, + "index": map[string]interface{}{"long": int64(0)}, + "block_time": map[string]interface{}{"long.timestamp-millis": timestamp}, + "producer_block_id": map[string]interface{}{"string": ""}, + "receipt": map[string]interface{}{ + "io.ultra.dkafka.transaction.TransactionReceiptHeader": map[string]interface{}{ + "cpu_usage_micro_seconds": nil, + "status": nil, + }}, + "elapsed": map[string]interface{}{"long": int64(0)}, + "net_usage": map[string]interface{}{"long": int64(0)}, + "action_traces": []interface{}{}, + "exception": map[string]interface{}{ + "io.ultra.dkafka.transaction.Exception": map[string]interface{}{ + "code": nil, + "message": nil, + "name": nil, + }}, + "error_code": map[string]interface{}{"bytes.decimal": new(big.Rat).SetInt64(0)}, + "ram_ops": []interface{}{}, + "creation_tree": []interface{}{}, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t1 *testing.T) { + abiCodec := NewStreamedAbiCodecWithTransaction(&DfuseAbiRepository{}, + nil, srclient.CreateMockSchemaRegistryClient("mock://bench-adapter"), "", "mock://bench-adapter", srclient.Forward) + codec, err := abiCodec.GetCodec(transactionSchema.AsCodecId(), 0) + if err != nil { + t.Fatalf("cannot load codec for %s", transactionNotification) + } + tgen := transactionGenerator{ + headers: default_headers, + topic: tt.fields.topic, + abiCodec: abiCodec, + } + messages, err := tgen.Apply(tt.args) + if (err != nil) != tt.wantErr { + t.Errorf("Apply() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(messages) > 1 { + t.Errorf("Apply() got = %v, want a list of message of len: 1", messages) + } + // test the value + gotValue, err := codec.Unmarshal(messages[0].Value) + if err != nil { + t.Errorf("Unmarshal() error = %v", err) + return + } + expect := tt.expect.value + if expect == nil { + expect = tt.args.transaction + } + if !reflect.DeepEqual(gotValue, expect) { + t.Errorf("codec Marshal() then Unmarshal() = %v, want %v", gotValue, expect) + } + // test the key + if string(messages[0].Key) != tt.expect.key { + t1.Errorf("Apply() got = %v, want %v", messages[0].Key, tt.expect.key) + } + + }) + } +} + +func Test_transactionGenerator_Apply_approval(t *testing.T) { + tests := []struct { + name string + file string + nbMessages int + }{ + { + "transaction-1", + "testdata/block-transactions-6723651.pb.json", + 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + block := &pbcodec.Block{} + err := jsonpb.UnmarshalString(string(readFileFromTestdata(t, tt.file)), block) + if err != nil { + t.Fatalf("jsonpb.UnmarshalString(): %v", err) + } + + abiCodec := NewStreamedAbiCodecWithTransaction(&DfuseAbiRepository{}, + nil, srclient.CreateMockSchemaRegistryClient("mock://bench-adapter"), "", "mock://bench-adapter", srclient.Forward) + + if err != nil { + t.Fatalf("cannot load codec for %s", transactionNotification) + } + transactionGen := transactionGenerator{ + headers: default_headers, + topic: "test.topic", + abiCodec: abiCodec, + } + for i, trx := range block.TransactionTraces() { + _, err := transactionGen.Apply(TransactionContext{ + block: block, + stepName: "NEW", + transaction: trx, + blockStep: BlockStep{ + blk: block, + step: pbbstream.ForkStep_STEP_NEW, + cursor: "???", + previousCursor: "???", + }, + }) + if err != nil { + t.Errorf("transactionGen.Apply() at %d with error = %v", i, err) + return + } + } + }) + } +}