diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index fac65a3a..ca8c3438 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -58,8 +58,8 @@ object Versions { const val Protobuf = "3.19.1" const val Grpc = "1.40.1" const val KotlinGrpc = "1.2.0" - const val GrpcStarter = "4.5.6" const val Postgres = "42.2.23" + const val Encryption = "0.4.0" // Testing const val Jupiter = "5.7.1" @@ -94,6 +94,7 @@ object Libraries { const val ApacheCommonsText = "org.apache.commons:commons-text:${Versions.ApacheCommonsText}" const val Khttp = "khttp:khttp:${Versions.Khttp}" const val KaseChange = "net.pearx.kasechange:kasechange:${Versions.KaseChange}" + const val Encryption = "io.provenance.scope:encryption:${Versions.Encryption}" // Logging const val LogbackCore = "ch.qos.logback.contrib:logback-json-core:${Versions.Logback}" diff --git a/database/src/main/resources/db/migration/V1_39__Add_scope_transfers.sql b/database/src/main/resources/db/migration/V1_39__Add_scope_transfers.sql new file mode 100644 index 00000000..6039ca38 --- /dev/null +++ b/database/src/main/resources/db/migration/V1_39__Add_scope_transfers.sql @@ -0,0 +1,57 @@ +SELECT 'Add nft_scope_value_owner' AS comment; +CREATE TABLE IF NOT EXISTS nft_scope_value_owner +( + id SERIAL PRIMARY KEY, + scope_id INT NOT NULL, + scope_addr VARCHAR(128) NOT NULL, + value_owner VARCHAR(256) NOT NULL, + tx_id INT NOT NULL, + block_height INT NOT NULL, + tx_hash VARCHAR(64) NOT NULL +); + +CREATE UNIQUE INDEX IF NOT EXISTS nft_scope_vo_scope_address_tx_idx + ON nft_scope_value_owner (scope_id, value_owner, tx_id); +CREATE INDEX IF NOT EXISTS nft_scope_vo_scope_id_idx ON nft_scope_value_owner (scope_id); +CREATE INDEX IF NOT EXISTS nft_scope_vo_scope_addr_idx ON nft_scope_value_owner (scope_addr); +CREATE INDEX IF NOT EXISTS nft_scope_vo_address_idx ON nft_scope_value_owner (value_owner); +CREATE INDEX IF NOT EXISTS nft_scope_vo_block_height_idx ON nft_scope_value_owner (block_height); + + +SELECT 'Add nft_scope_owner' AS comment; +CREATE TABLE IF NOT EXISTS nft_scope_owner +( + id SERIAL PRIMARY KEY, + scope_id INT NOT NULL, + scope_addr VARCHAR(128) NOT NULL, + owners_data JSONB NOT NULL, + owners_data_hash TEXT NOT NULL, + tx_id INT NOT NULL, + block_height INT NOT NULL, + tx_hash VARCHAR(64) NOT NULL +); + +CREATE UNIQUE INDEX IF NOT EXISTS nft_scope_owner_scope_owners_data_tx_idx + ON nft_scope_owner (scope_id, owners_data_hash, tx_id); +CREATE INDEX IF NOT EXISTS nft_scope_owner_scope_id_idx ON nft_scope_owner (scope_id); +CREATE INDEX IF NOT EXISTS nft_scope_owner_scope_addr_idx ON nft_scope_owner (scope_addr); +CREATE INDEX IF NOT EXISTS nft_scope_owner_block_height_idx ON nft_scope_owner (block_height); + +SELECT 'Add nft_scope_data_access' AS comment; +CREATE TABLE IF NOT EXISTS nft_scope_data_access +( + id SERIAL PRIMARY KEY, + scope_id INT NOT NULL, + scope_addr VARCHAR(128) NOT NULL, + access_data JSONB NOT NULL, + access_data_hash TEXT NOT NULL, + tx_id INT NOT NULL, + block_height INT NOT NULL, + tx_hash VARCHAR(64) NOT NULL +); + +CREATE UNIQUE INDEX IF NOT EXISTS nft_scope_data_access_scope_access_data_tx_idx + ON nft_scope_data_access (scope_id, access_data_hash, tx_id); +CREATE INDEX IF NOT EXISTS nft_scope_data_access_scope_id_idx ON nft_scope_data_access (scope_id); +CREATE INDEX IF NOT EXISTS nft_scope_data_access_scope_addr_idx ON nft_scope_data_access (scope_addr); +CREATE INDEX IF NOT EXISTS nft_scope_data_access_block_height_idx ON nft_scope_data_access (block_height); diff --git a/docs/nft/OwnerUpdates.md b/docs/nft/OwnerUpdates.md index 2060ba63..a9949930 100644 --- a/docs/nft/OwnerUpdates.md +++ b/docs/nft/OwnerUpdates.md @@ -1 +1,110 @@ -Track when a scope owner/value owner/data access is initiated or updated. +# Owner / Valueowner / Data Access changes +* track when the values change +* use to count how many scopes the address owns / value owns + +### Owner changes +* Can only be updated through a WriteScopeRequest or {Add|Delete}ScopeOwnerRequest +* can be 1 or more entries + +### ValueOwner changes +* Can only be updated through a WriteScopeRequest +* only 1 entry + +### Data Access changes +* Can be updated by P8eMemorializeContract - Adds only +* Can be updated through a WriteScopeRequest or {Add|Delete}ScopeDataAccess +* Can be 0 or more entries + + +## Owner Changes +* `nft_scope_owner` + * id SERIAL PRIMARY KEY, + * scope_id INT NOT NULL, + * scope_addr VARCHAR(128) NOT NULL, + * tx_id INT NOT NULL, + * block_height INT NOT NULL, + * tx_hash VARCHAR(64) NOT NULL, + * owners_data JSONB NOT NULL, + * owners_data_hash TEXT NOT NULL + +* owners_data object + * list + * owner + * role - list + +* on insert, check for latest data, and compare hashes + * to ensure compatibility, order owners alphabetically, then save/compare/hash + +* on fetch, gather list of objects, sorted by height + * first record, all adds + * second record, + * if new address, add + * if address doesnt exist, removal + + +## Value Owner Changes +* `nft_scope_value_owner` + * id SERIAL PRIMARY KEY, + * scope_id INT NOT NULL, + * scope_addr VARCHAR(128) NOT NULL, + * tx_id INT NOT NULL, + * block_height INT NOT NULL, + * tx_hash VARCHAR(64) NOT NULL, + * value_owner VARCHAR(256) NOT NULL + +* on insert, check for latest data, and compare value_owner + +* on fetch, gather list of objects, sorted by height + + +## Data Access Changes +* `nft_scope_owner` + * id SERIAL PRIMARY KEY, + * scope_id INT NOT NULL, + * scope_addr VARCHAR(128) NOT NULL, + * tx_id INT NOT NULL, + * block_height INT NOT NULL, + * tx_hash VARCHAR(64) NOT NULL, + * access_data JSONB NOT NULL, + * access_data_hash TEXT NOT NULL + +* access_data object + * address - list + +* on insert, check for latest data, and compare hashes + * to ensure compatibility, order addresses alphabetically, then save/compare/hash + +* on fetch, gather list of objects, sorted by height + * first record, all adds + * second record, + * if new address, add + * if address doesnt exist, removal + + +## Scraping +* ensure its a successful tx before writing records + +* p8e memorialize + * will need to scrape for the fields + * will need to use the encryption stuff to decipher +``` + p8EData.Scope.Owners = contractRecitalParties + p8EData.Scope.DataAccess = partyAddresses(contractRecitalParties) + p8EData.Scope.ValueOwnerAddress, err = getValueOwner(msg.Contract.Invoker, msg.Contract.Recitals) + if err != nil { + return p8EData, err + } +``` + +* Write Scope + * scrape for the fields + +* {Add|Delete}Scope{Owner|DataAccess} + * scrape for fields + * take last set of data, update and insert into table + +## API +* Fetch value owner changes +* Fetch owner changes +* Fetch data access changes + diff --git a/service/build.gradle.kts b/service/build.gradle.kts index feeb6495..3bd6ac37 100644 --- a/service/build.gradle.kts +++ b/service/build.gradle.kts @@ -40,6 +40,10 @@ dependencies { api(Libraries.ApacheCommonsText) api(Libraries.Khttp) implementation(Libraries.KaseChange) + api(Libraries.Encryption) { + exclude("io.provenance.scope", "contract-proto") + exclude("io.provenance.protobuf", "pb-proto-java") + } implementation(Libraries.GrpcNetty) diff --git a/service/src/main/kotlin/io/provenance/explorer/grpc/v1/AccountGrpcClient.kt b/service/src/main/kotlin/io/provenance/explorer/grpc/v1/AccountGrpcClient.kt index 1a739a01..18fd5456 100644 --- a/service/src/main/kotlin/io/provenance/explorer/grpc/v1/AccountGrpcClient.kt +++ b/service/src/main/kotlin/io/provenance/explorer/grpc/v1/AccountGrpcClient.kt @@ -43,9 +43,9 @@ class AccountGrpcClient(channelUri: URI) { it.usePlaintext() } } - .idleTimeout(5, TimeUnit.MINUTES) - .keepAliveTime(60, TimeUnit.SECONDS) - .keepAliveTimeout(20, TimeUnit.SECONDS) + .idleTimeout(60, TimeUnit.SECONDS) + .keepAliveTime(10, TimeUnit.SECONDS) + .keepAliveTimeout(10, TimeUnit.SECONDS) .intercept(GrpcLoggingInterceptor()) .build() diff --git a/service/src/main/kotlin/io/provenance/explorer/grpc/v1/MarkerGrpcClient.kt b/service/src/main/kotlin/io/provenance/explorer/grpc/v1/MarkerGrpcClient.kt index 099f8d53..50889a9b 100644 --- a/service/src/main/kotlin/io/provenance/explorer/grpc/v1/MarkerGrpcClient.kt +++ b/service/src/main/kotlin/io/provenance/explorer/grpc/v1/MarkerGrpcClient.kt @@ -33,9 +33,9 @@ class MarkerGrpcClient(channelUri: URI, private val semaphore: Semaphore) { it.usePlaintext() } } - .idleTimeout(5, TimeUnit.MINUTES) - .keepAliveTime(60, TimeUnit.SECONDS) - .keepAliveTimeout(20, TimeUnit.SECONDS) + .idleTimeout(60, TimeUnit.SECONDS) + .keepAliveTime(10, TimeUnit.SECONDS) + .keepAliveTimeout(10, TimeUnit.SECONDS) .intercept(GrpcLoggingInterceptor()) .build() diff --git a/service/src/main/kotlin/io/provenance/explorer/service/async/AsyncCaching.kt b/service/src/main/kotlin/io/provenance/explorer/service/async/AsyncCaching.kt index a959d1dc..d667b686 100644 --- a/service/src/main/kotlin/io/provenance/explorer/service/async/AsyncCaching.kt +++ b/service/src/main/kotlin/io/provenance/explorer/service/async/AsyncCaching.kt @@ -501,14 +501,6 @@ class AsyncCaching( it.content.unpack(cosmwasm.wasm.v1.Proposal.StoreCodeProposal::class.java) } } - // translate content type - // store code -> hash wasm bytes - // -> add to retry table - // -> when future txs use same wasm bytes, link up records - // -> could also monitor proposals, if this proposal passes, check for next code id - // -> if bytes match proposal bytes, link up record as creation height - - // How does a store code get created? Is it just an event? Is it tied to a block? Does a msg get dispatched? } fun saveSignaturesTx(tx: ServiceOuterClass.GetTxResponse) = transaction {