diff --git a/.gitignore b/.gitignore index 3dc4b4e8..386968d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ - +**/*~ /target **/*.rs.bk .env diff --git a/Cargo.toml b/Cargo.toml index 77a40e2f..e78b22c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aepp-middleware" -version = "0.10.0" +version = "0.11.0" authors = ["John Newby <@johnsnewby>", "Shubhendu Shekhar <@shekhar-shubhendu>", "Andrea Giacobino <@noandrea>" ] description = "æternity node middleware: A caching and reporting layer for aeternity blockchain" diff --git a/conf/log4rs.yaml b/conf/log4rs-example.yaml similarity index 98% rename from conf/log4rs.yaml rename to conf/log4rs-example.yaml index f01f9d01..d9d30305 100644 --- a/conf/log4rs.yaml +++ b/conf/log4rs-example.yaml @@ -39,5 +39,4 @@ root: level: debug appenders: - main - - email - stdout diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 6fb37e50..d57c0b41 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -15,7 +15,7 @@ FROM node:10.16.3-stretch as frontend WORKDIR /app COPY --from=frontend-build /app/.nuxt /app/.nuxt COPY --from=frontend-build /app/static /app/static -RUN npm install nuxt@2.9.2 @nuxtjs/axios@5.5.4 @download/blockies@1.0.3 clipboard-copy@3.0.0 vue-multiselect@2.1.6 bignumber.js@9.0.0 && \ +RUN npm install nuxt@2.9.2 @nuxtjs/axios@5.5.4 @download/blockies@1.0.3 clipboard-copy@3.0.0 vue-multiselect@2.1.6 bignumber.js@9.0.0 vue-slider-component@3.0.41 && \ npm cache clean --force COPY package.json package.json COPY LICENSE.md LICENSE.md diff --git a/frontend/assets/sprite/svg/ae-loader.svg b/frontend/assets/sprite/svg/ae-loader/ae-loader.svg similarity index 100% rename from frontend/assets/sprite/svg/ae-loader.svg rename to frontend/assets/sprite/svg/ae-loader/ae-loader.svg diff --git a/frontend/assets/sprite/svg/back.svg b/frontend/assets/sprite/svg/back/back.svg similarity index 100% rename from frontend/assets/sprite/svg/back.svg rename to frontend/assets/sprite/svg/back/back.svg diff --git a/frontend/assets/sprite/svg/burger.svg b/frontend/assets/sprite/svg/burger/burger.svg similarity index 100% rename from frontend/assets/sprite/svg/burger.svg rename to frontend/assets/sprite/svg/burger/burger.svg diff --git a/frontend/assets/sprite/svg/calendar.svg b/frontend/assets/sprite/svg/calendar/calendar.svg similarity index 100% rename from frontend/assets/sprite/svg/calendar.svg rename to frontend/assets/sprite/svg/calendar/calendar.svg diff --git a/frontend/assets/sprite/svg/check.svg b/frontend/assets/sprite/svg/check/check.svg similarity index 100% rename from frontend/assets/sprite/svg/check.svg rename to frontend/assets/sprite/svg/check/check.svg diff --git a/frontend/assets/sprite/svg/close.svg b/frontend/assets/sprite/svg/close/close.svg similarity index 100% rename from frontend/assets/sprite/svg/close.svg rename to frontend/assets/sprite/svg/close/close.svg diff --git a/frontend/assets/sprite/svg/copy.svg b/frontend/assets/sprite/svg/copy/copy.svg similarity index 100% rename from frontend/assets/sprite/svg/copy.svg rename to frontend/assets/sprite/svg/copy/copy.svg diff --git a/frontend/assets/sprite/svg/delete.svg b/frontend/assets/sprite/svg/delete/delete.svg similarity index 100% rename from frontend/assets/sprite/svg/delete.svg rename to frontend/assets/sprite/svg/delete/delete.svg diff --git a/frontend/assets/sprite/svg/eye.svg b/frontend/assets/sprite/svg/eye/eye.svg similarity index 100% rename from frontend/assets/sprite/svg/eye.svg rename to frontend/assets/sprite/svg/eye/eye.svg diff --git a/frontend/assets/sprite/svg/filter.svg b/frontend/assets/sprite/svg/filter/filter.svg similarity index 100% rename from frontend/assets/sprite/svg/filter.svg rename to frontend/assets/sprite/svg/filter/filter.svg diff --git a/frontend/assets/sprite/svg/info.svg b/frontend/assets/sprite/svg/info/info.svg similarity index 100% rename from frontend/assets/sprite/svg/info.svg rename to frontend/assets/sprite/svg/info/info.svg diff --git a/frontend/assets/sprite/svg/left-more.svg b/frontend/assets/sprite/svg/left-more/left-more.svg similarity index 100% rename from frontend/assets/sprite/svg/left-more.svg rename to frontend/assets/sprite/svg/left-more/left-more.svg diff --git a/frontend/assets/sprite/svg/plus.svg b/frontend/assets/sprite/svg/plus/plus.svg similarity index 100% rename from frontend/assets/sprite/svg/plus.svg rename to frontend/assets/sprite/svg/plus/plus.svg diff --git a/frontend/assets/sprite/svg/reload.svg b/frontend/assets/sprite/svg/reload/reload.svg similarity index 100% rename from frontend/assets/sprite/svg/reload.svg rename to frontend/assets/sprite/svg/reload/reload.svg diff --git a/frontend/assets/sprite/svg/search.svg b/frontend/assets/sprite/svg/search/search.svg similarity index 100% rename from frontend/assets/sprite/svg/search.svg rename to frontend/assets/sprite/svg/search/search.svg diff --git a/frontend/assets/sprite/svg/settings.svg b/frontend/assets/sprite/svg/settings/settings.svg similarity index 100% rename from frontend/assets/sprite/svg/settings.svg rename to frontend/assets/sprite/svg/settings/settings.svg diff --git a/frontend/assets/sprite/svg/share.svg b/frontend/assets/sprite/svg/share/share.svg similarity index 100% rename from frontend/assets/sprite/svg/share.svg rename to frontend/assets/sprite/svg/share/share.svg diff --git a/frontend/components/appIcon/index.vue b/frontend/components/appIcon/index.vue index dfe4ebfb..752e6665 100644 --- a/frontend/components/appIcon/index.vue +++ b/frontend/components/appIcon/index.vue @@ -1,6 +1,6 @@ diff --git a/frontend/nuxt.config.js b/frontend/nuxt.config.js index 3b541b0b..69305291 100644 --- a/frontend/nuxt.config.js +++ b/frontend/nuxt.config.js @@ -37,8 +37,8 @@ module.exports = { } ], env: { - middlewareURL: process.env.NUXT_APP_NODE_URL || 'https://testnet.mdw.aepps.com', - middlewareWS: process.env.NUXT_APP_NODE_WS || 'wss://testnet.mdw.aepps.com/websocket', + middlewareURL: process.env.NUXT_APP_NODE_URL || 'https://testnet.aeternal.io', + middlewareWS: process.env.NUXT_APP_NODE_WS || 'wss://testnet.aeternal.io/websocket', networkName: process.env.NUXT_APP_NETWORK_NAME || 'TEST NET', swaggerHub: process.env.NUXT_APP_SWAGGER_HUB || 'https://app.swaggerhub.com/apis-docs/sshekhar/aepp-middleware/1.0', enableFaucet: process.env.NUXT_APP_ENABLE_FAUCET || false, @@ -49,7 +49,8 @@ module.exports = { */ plugins: [ { src: '~/plugins/directives/copyToClipboard.js' }, - { src: '~/plugins/directives/removeSpacesOnCopy.js' } + { src: '~/plugins/directives/removeSpacesOnCopy.js' }, + { src: '~/plugins/directives/vueSliderComponent.js', mode: 'client' } ], /* ** Router config diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7d5db81e..5953fc17 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10641,6 +10641,11 @@ "vue": "2.*.*" } }, + "vue-class-component": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-7.1.0.tgz", + "integrity": "sha512-G9152NzUkz0i0xTfhk0Afc8vzdXxDR1pfN4dTwE72cskkgJtdXfrKBkMfGvDuxUh35U500g5Ve4xL8PEGdWeHg==" + }, "vue-clickaway": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/vue-clickaway/-/vue-clickaway-2.2.2.tgz", @@ -10738,6 +10743,14 @@ "resolved": "https://registry.npmjs.org/vue-no-ssr/-/vue-no-ssr-1.1.1.tgz", "integrity": "sha512-ZMjqRpWabMPqPc7gIrG0Nw6vRf1+itwf0Itft7LbMXs2g3Zs/NFmevjZGN1x7K3Q95GmIjWbQZTVerxiBxI+0g==" }, + "vue-property-decorator": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/vue-property-decorator/-/vue-property-decorator-8.3.0.tgz", + "integrity": "sha512-77YtTbZHd5CNiPzbqv51kEgL48yvD2dUDfF28vEyw3MbQ9bBAb/tDyFzskcqjNRbWyXk1vq4oM2CK/LfhxuIBg==", + "requires": { + "vue-class-component": "^7.1.0" + } + }, "vue-router": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.0.7.tgz", @@ -10805,6 +10818,14 @@ } } }, + "vue-slider-component": { + "version": "3.0.41", + "resolved": "https://registry.npmjs.org/vue-slider-component/-/vue-slider-component-3.0.41.tgz", + "integrity": "sha512-TVLwAJtDInkYp3KYUqptvyLg9Ppx4WqCeVlbbqCS10MpLItq10yRhObWVAGcN07n2++Vv7h5y//4hLGZkauBZA==", + "requires": { + "vue-property-decorator": "^8.0.0" + } + }, "vue-style-loader": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index bba4a8cc..b5a0ab91 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,7 +28,8 @@ "normalize-scss": "7.0.1", "nuxt": "2.9.2", "vue": "2.6.10", - "vue-multiselect": "^2.1.6" + "vue-multiselect": "2.1.6", + "vue-slider-component": "3.0.41" }, "devDependencies": { "@vue/eslint-config-standard": "4.0.0", diff --git a/frontend/pages/auctions/index.vue b/frontend/pages/auctions/index.vue new file mode 100644 index 00000000..2481470d --- /dev/null +++ b/frontend/pages/auctions/index.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/frontend/partials/appMainNav/index.vue b/frontend/partials/appMainNav/index.vue index b86ac04a..10eb7c0c 100644 --- a/frontend/partials/appMainNav/index.vue +++ b/frontend/partials/appMainNav/index.vue @@ -36,6 +36,9 @@ Names + + Name Auctions + Oracles diff --git a/frontend/partials/names/nameAuction/index.vue b/frontend/partials/names/nameAuction/index.vue new file mode 100644 index 00000000..1f015bba --- /dev/null +++ b/frontend/partials/names/nameAuction/index.vue @@ -0,0 +1,186 @@ + + + + diff --git a/frontend/partials/names/nameAuctionList/index.vue b/frontend/partials/names/nameAuctionList/index.vue new file mode 100644 index 00000000..13a9a3a0 --- /dev/null +++ b/frontend/partials/names/nameAuctionList/index.vue @@ -0,0 +1,23 @@ + + + + diff --git a/frontend/plugins/directives/vueSliderComponent.js b/frontend/plugins/directives/vueSliderComponent.js new file mode 100644 index 00000000..4cac4bf6 --- /dev/null +++ b/frontend/plugins/directives/vueSliderComponent.js @@ -0,0 +1,4 @@ +import Vue from 'vue' +import VueSlider from 'vue-slider-component' + +Vue.component('vue-slider', VueSlider) diff --git a/frontend/store/contracts.js b/frontend/store/contracts.js index b78aea56..dfeffaf4 100644 --- a/frontend/store/contracts.js +++ b/frontend/store/contracts.js @@ -24,6 +24,7 @@ export const actions = { } catch (e) { console.log(e) commit('catchError', 'Error', { root: true }) + return [] } }, @@ -36,6 +37,7 @@ export const actions = { } catch (e) { console.log(e) commit('catchError', 'Error', { root: true }) + return { transactions: [] } } }, getContractCalls: async function ({ rootState: { nodeUrl }, commit }, contractId) { @@ -47,6 +49,7 @@ export const actions = { } catch (e) { console.log(e) commit('catchError', 'Error', { root: true }) + return [] } } } diff --git a/frontend/store/names.js b/frontend/store/names.js index 202babbe..a692dbbb 100644 --- a/frontend/store/names.js +++ b/frontend/store/names.js @@ -32,5 +32,20 @@ export const actions = { console.log(e) commit('catchError', 'Error', { root: true }) } + }, + getActiveNameAuctions: async function ({ rootState: { nodeUrl }, commit }, { page, limit, sort, length }) { + try { + let url = `${nodeUrl}/middleware/names/auctions/active?limit=${limit}&page=${page}&sort=${sort}` + if (length > 0) { + url += `&length=${length}` + } + const auctions = await axios.get(url) + console.info('MDW 🔗 ' + url) + return auctions.data + } catch (e) { + console.log(e) + commit('catchError', 'Error', { root: true }) + return [] + } } } diff --git a/frontend/styles/index.scss b/frontend/styles/index.scss index f3bb3f31..1f121b18 100644 --- a/frontend/styles/index.scss +++ b/frontend/styles/index.scss @@ -54,4 +54,8 @@ .multiselect__spinner:before { border-top-color: #FF0D6A; } -} \ No newline at end of file +} + +$themeColor: #ff0a6a; +$bgColor: #ffa8ca; +@import '~vue-slider-component/lib/theme/material.scss'; diff --git a/migrations/2019-10-15-105814_name_auction_views/up.sql b/migrations/2019-10-15-105814_name_auction_views/up.sql index 37f33530..7d766a3a 100644 --- a/migrations/2019-10-15-105814_name_auction_views/up.sql +++ b/migrations/2019-10-15-105814_name_auction_views/up.sql @@ -24,14 +24,15 @@ SELECT FROM transactions WHERE tx_type='NameClaimTx' AND - (tx->>'name_salt')::NUMERIC = 0 AND + (tx->>'name_salt')::NUMERIC <> 0 AND block_height >= get_fork_height(4) GROUP BY tx->>'name'; CREATE OR REPLACE VIEW winning_bids AS SELECT t.tx->>'name' AS name, - (MAX(t.tx->>'name_fee'))::numeric AS winning_bid + MAX((t.tx->>'name_fee')::numeric) AS winning_bid, + MAX(t.block_height) AS height FROM transactions t JOIN all_names an ON t.tx->>'name' = an.name WHERE @@ -42,7 +43,8 @@ GROUP BY CREATE OR REPLACE VIEW name_auction_entries AS SELECT an.name AS name, - an.auction_expiration AS expiration, + wb.height + lima_name_auction_timeout(an.name)::BIGINT + AS expiration, wb.winning_bid AS winning_bid, (t.tx->>'account_id')::VARCHAR AS winning_bidder, t.id AS transaction_id diff --git a/migrations/2019-11-02-100848_fix-auctions-views-again/down.sql b/migrations/2019-11-02-100848_fix-auctions-views-again/down.sql new file mode 100644 index 00000000..291a97c5 --- /dev/null +++ b/migrations/2019-11-02-100848_fix-auctions-views-again/down.sql @@ -0,0 +1 @@ +-- This file should undo anything in `up.sql` \ No newline at end of file diff --git a/migrations/2019-11-02-100848_fix-auctions-views-again/up.sql b/migrations/2019-11-02-100848_fix-auctions-views-again/up.sql new file mode 100644 index 00000000..8a7d9a0b --- /dev/null +++ b/migrations/2019-11-02-100848_fix-auctions-views-again/up.sql @@ -0,0 +1,27 @@ + +CREATE OR REPLACE VIEW winning_bids AS +SELECT + t.tx->>'name' AS name, + (MAX(t.tx->>'name_fee'))::numeric AS winning_bid, + MAX(t.block_height) AS height +FROM + transactions t JOIN all_names an ON t.tx->>'name' = an.name +WHERE + t.block_height >= an.start_block_height +GROUP BY + tx->>'name'; + +CREATE OR REPLACE VIEW name_auction_entries AS +SELECT + an.name AS name, + wb.height + lima_name_auction_timeout(an.name)::BIGINT + AS expiration, + wb.winning_bid AS winning_bid, + (t.tx->>'account_id')::VARCHAR AS winning_bidder, + t.id AS transaction_id +FROM + transactions t, all_names an, winning_bids wb +WHERE + t.tx->>'name' = an.name AND + t.tx->>'name' = wb.name AND + (t.tx->>'name_fee')::numeric = wb.winning_bid; diff --git a/migrations/2019-11-04-110845_names-case-insensitive-and-materialized-views/down.sql b/migrations/2019-11-04-110845_names-case-insensitive-and-materialized-views/down.sql new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/migrations/2019-11-04-110845_names-case-insensitive-and-materialized-views/down.sql @@ -0,0 +1 @@ + diff --git a/migrations/2019-11-04-110845_names-case-insensitive-and-materialized-views/up.sql b/migrations/2019-11-04-110845_names-case-insensitive-and-materialized-views/up.sql new file mode 100644 index 00000000..543dd7fe --- /dev/null +++ b/migrations/2019-11-04-110845_names-case-insensitive-and-materialized-views/up.sql @@ -0,0 +1,49 @@ +UPDATE names SET name=LOWER(name); + +CREATE OR REPLACE VIEW all_names AS +SELECT + LOWER(tx->>'name') AS name, + MAX(block_height) AS start_block_height, + (MAX(block_height) + lima_name_auction_timeout(tx->>'name'))::BIGINT + AS auction_expiration +FROM transactions +WHERE + tx_type='NameClaimTx' AND + (tx->>'name_salt')::NUMERIC <> 0 AND + block_height >= get_fork_height(4) +GROUP BY tx->>'name'; + +CREATE OR REPLACE VIEW winning_bids AS +SELECT + an.name, + MAX((t.tx->>'name_fee')::numeric) AS winning_bid, + MAX(t.block_height) AS height +FROM + transactions t JOIN all_names an ON LOWER(t.tx->>'name') = an.name +WHERE + t.block_height >= an.start_block_height +GROUP BY + an.name; + +DROP VIEW IF EXISTS name_auction_entries; + +CREATE MATERIALIZED VIEW name_auction_entries AS +SELECT + an.name AS name, + wb.height + lima_name_auction_timeout(an.name)::BIGINT + AS expiration, + wb.winning_bid AS winning_bid, + (t.tx->>'account_id')::VARCHAR AS winning_bidder, + t.id AS transaction_id +FROM + transactions t, all_names an, winning_bids wb +WHERE + LOWER(t.tx->>'name') = an.name AND + LOWER(t.tx->>'name') = wb.name AND + t.block_height >= an.start_block_height AND + (t.tx->>'name_fee')::numeric = wb.winning_bid +WITH DATA; + +COMMIT; + +CREATE UNIQUE INDEX name_auction_entries_name_idx ON name_auction_entries(name); diff --git a/src/hashing.rs b/src/hashing.rs index ba453a13..1d3fb70b 100644 --- a/src/hashing.rs +++ b/src/hashing.rs @@ -65,26 +65,6 @@ fn blake2bdigest(v: &Vec) -> Vec { hasher.vec_result() } -pub fn gen_oracle_query_id(sender_id: &String, nonce: i64, recipient_id: &String) -> String { - let mut sender_id_bin = decodebase58check(&hash_part(sender_id)); - let mut recipient_id_bin = decodebase58check(&hash_part(recipient_id)); - let mut nonce_byte32 = min_b(nonce); - loop { - if nonce_byte32.len() < 32 { - nonce_byte32.insert(0, 0u8); - } else { - break; - } - } - let mut all = vec![]; - all.append(&mut sender_id_bin); - all.append(&mut nonce_byte32); - all.append(&mut recipient_id_bin); - let hash = blake2bdigest(&all); - let encoded = to_base58check(&hash); - format!("oq_{}", encoded) -} - pub fn gen_channel_id( initiator_id: &String, channel_create_tx_nonce: i64, @@ -109,50 +89,68 @@ pub fn gen_channel_id( format!("ch_{}", encoded) } -pub fn get_name_hash(name: &str) -> Vec { - let mut result = [0u8; 32].to_vec(); - let mut split: Vec<&[u8]> = name.split('.').rev().map(|s| s.as_bytes()).collect(); +pub fn gen_oracle_query_id(sender_id: &String, nonce: i64, recipient_id: &String) -> String { + let mut sender_id_bin = decodebase58check(&hash_part(sender_id)); + let mut recipient_id_bin = decodebase58check(&hash_part(recipient_id)); + let mut nonce_byte32 = min_b(nonce); loop { - if let Some(part) = split.pop() { - let mut hasher = VarBlake2b::new(32).unwrap(); - hasher.input(part); - let hashed = hasher.vec_result(); - result.extend(hashed); - let mut hasher = VarBlake2b::new(32).unwrap(); - hasher.input(result); - result = hasher.vec_result(); + if nonce_byte32.len() < 32 { + nonce_byte32.insert(0, 0u8); } else { break; } } - result + let mut all = vec![]; + all.append(&mut sender_id_bin); + all.append(&mut nonce_byte32); + all.append(&mut recipient_id_bin); + let hash = blake2bdigest(&all); + let encoded = to_base58check(&hash); + format!("oq_{}", encoded) +} + +fn blake2b(input: Vec) -> Vec { + let mut hasher = VarBlake2b::new(32).unwrap(); + hasher.input(input); + hasher.vec_result() +} + +pub fn get_name_id(name: &str) -> MiddlewareResult { + Ok(format!( + "nm_{}", + to_base58check(&blake2b( + name.to_string().to_lowercase().as_bytes().to_vec() + )) + )) } #[test] fn test_name_hash() { assert_eq!( - get_name_id("welghmolql.test").unwrap(), - "nm_Ziiq3M9ASEHXCV71qUNde6SsomqwZjYPFvnJSvTkpSUDiXqH3" + get_name_id("morethantwelve.chain").unwrap(), + "nm_2JiYeYyL4qgTm7Rb16AG1LuUWGHdyuH6v8uRcJ2Gfqto9ezBFR" ); - assert_ne!( - get_name_id("abc.test").unwrap(), - "nm_2KrC4asc6fdv82uhXDwfiqB1TY2htjhnzwzJJKLxidyMymJRUQ" + assert_eq!( + get_name_id("thisismysecond.chain").unwrap(), + "nm_gE3vy9S56bX8sxLMTc32m6AjNwzk4uiyspFuUV72eiqULHWLQ" ); + assert_eq!( + get_name_id("example21.chain").unwrap(), + "nm_2tMJx8cXGy2oy7efC1FSne3EifksFYnm1KyckDYGvyYfn5GKUC"); } -pub fn get_name_id(name: &str) -> MiddlewareResult { - Ok(format!("nm_{}", to_base58check(&get_name_hash(name)))) -} pub fn get_name_auction_length(name: &String) -> MiddlewareResult { let parts: Vec<&str> = name.split(".").collect(); if parts.len() != 2 { - return Err(crate::middleware_result::MiddlewareError::new(format!("name {} not supported", name).as_str())); + return Err(crate::middleware_result::MiddlewareError::new( + format!("name {} not supported", name).as_str(), + )); } let length = match String::from(*parts.get(0)?).len() { - 1 ..=4 => 29760, - 5 ..=8 => 14880, - 9 ..=12 => 480, + 1..=4 => 29760, + 5..=8 => 14880, + 9..=12 => 480, _ => 0, }; Ok(length) @@ -160,11 +158,26 @@ pub fn get_name_auction_length(name: &String) -> MiddlewareResult { #[test] fn test_name_auction_length() { - assert_eq!(get_name_auction_length(&String::from("1.chain")).unwrap(), 29760); - assert_eq!(get_name_auction_length(&String::from("12345678.chain")).unwrap(), 14880); - assert_eq!(get_name_auction_length(&String::from("123456789.chain")).unwrap(), 480); - assert_eq!(get_name_auction_length(&String::from("1234567890.chain")).unwrap(), 480); - assert_eq!(get_name_auction_length(&String::from("12345467890123.chain")).unwrap(), 0); + assert_eq!( + get_name_auction_length(&String::from("1.chain")).unwrap(), + 29760 + ); + assert_eq!( + get_name_auction_length(&String::from("12345678.chain")).unwrap(), + 14880 + ); + assert_eq!( + get_name_auction_length(&String::from("123456789.chain")).unwrap(), + 480 + ); + assert_eq!( + get_name_auction_length(&String::from("1234567890.chain")).unwrap(), + 480 + ); + assert_eq!( + get_name_auction_length(&String::from("12345467890123.chain")).unwrap(), + 0 + ); } /* diff --git a/src/loader.rs b/src/loader.rs index 00ca087c..a8aed5b0 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -382,17 +382,24 @@ impl BlockLoader { pub fn load_blocks(&self, _height: i64) -> MiddlewareResult<(i32, i32)> { let connection = PGCONNECTION.get()?; - let result = connection.transaction::<(i32, i32), MiddlewareError, _>(|| { - self.internal_load_block(&connection, _height) - }); - result + let (key_block_id, count, name_update_needed) = connection + .transaction::<(i32, i32, bool), MiddlewareError, _>(|| { + self.internal_load_block(&connection, _height) + })?; + debug!("name_update_needed: {:?}", name_update_needed); + if name_update_needed { + debug!("Generating name event"); + Name::generate_event()?; + } + Ok((key_block_id, count)) } fn internal_load_block( &self, connection: &PgConnection, _height: i64, - ) -> MiddlewareResult<(i32, i32)> { + ) -> MiddlewareResult<(i32, i32, bool)> { + let mut name_update_needed: bool = false; let mut count = 0; // clear out the block at this height, and any with the same hash, to prevent key violations. diesel::delete(key_blocks.filter(height.eq(&_height))).execute(connection)?; @@ -435,10 +442,20 @@ impl BlockLoader { Some(_micro_block_id), )?; BlockLoader::update_auxiliary_tables(&connection, tx_id, &transaction)?; + debug!( + "Is name tx: {:?}, {}", + transaction.is_name_transaction(), + transaction.tx["type"].as_str().unwrap() + ); + if transaction.is_name_transaction() { + name_update_needed = true; + // have to return this because we're in a transaction and the materialized view + // must update asynchronously (and only once for a key block). + } } count += 1; } - Ok((key_block_id, count)) + Ok((key_block_id, count, name_update_needed)) } /* @@ -484,6 +501,18 @@ impl BlockLoader { // this only stores the initial ClaimTx, the subsequent bidding ones are // not relevant to this table. name.save(connection)?; + } else { + let name = transaction.tx["name"].as_str()?; + if let Some(mut name) = Name::load_for_name(connection, &name.to_string()) { + name.auction_end_height = (transaction.block_height + + crate::hashing::get_name_auction_length( + &transaction.tx["name"].as_str()?.to_string(), + )?) + .into(); + name.update(connection)?; + } else { + error!("Couldn't load name {}", name); + } } } "NameRevokeTx" => { @@ -557,7 +586,12 @@ impl BlockLoader { data: serde_json::to_value(trans)?, })?; //broadcast transaction let transaction_id: i32 = _tx.save(conn)?; - let associated_accounts = crate::models::InsertableAssociatedAccount::from_transaction(conn, &serde_json::to_value(trans)?, trans.block_height as i64, transaction_id)?; + let associated_accounts = crate::models::InsertableAssociatedAccount::from_transaction( + conn, + &serde_json::to_value(trans)?, + trans.block_height as i64, + transaction_id, + )?; for associated_account in associated_accounts { associated_account.save(conn)?; } diff --git a/src/main.rs b/src/main.rs index a8c46429..b48ed76e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -141,13 +141,44 @@ fn setup_protocols(url: String) { for protocol in protocols { let effective_at_height = protocol["effective_at_height"].as_i64().unwrap(); let version = protocol["version"].as_i64().unwrap(); - connection.execute( - "INSERT INTO protocols(effective_at_height, version) VALUES ($1, $2)", - &[&effective_at_height, &version]).unwrap(); + connection + .execute( + "INSERT INTO protocols(effective_at_height, version) VALUES ($1, $2)", + &[&effective_at_height, &version], + ) + .unwrap(); } trans.commit().unwrap(); } +/** + * So far there is only one materialized view, that for + * name_auction_entries. It takes a long time (~30s as of 11/2019) to + * populate, hence this solution. + */ +fn refresh_materialized_views() -> MiddlewareResult<()> { + fn _refresh() -> MiddlewareResult<()> { + debug!("Waiting for name event"); + crate::models::Name::wait_for_event()?; + debug!("Received name event"); + let connection = crate::loader::SQLCONNECTION.get()?; + connection.execute( + "REFRESH MATERIALIZED VIEW CONCURRENTLY name_auction_entries", + &[], + )?; + Ok(()) + } + thread::spawn(|| loop { + match _refresh() { + Ok(_) => (), + Err(e) => { + error!("Error refreshing name view: {:?}", e); + } + } + }); + Ok(()) +} + fn main() { dotenv::dotenv().ok(); @@ -227,6 +258,7 @@ fn main() { } migration_result.unwrap(); setup_protocols(url.clone()); + refresh_materialized_views().unwrap(); } /* diff --git a/src/middleware_result.rs b/src/middleware_result.rs index fc76b82f..0402fa52 100644 --- a/src/middleware_result.rs +++ b/src/middleware_result.rs @@ -76,7 +76,7 @@ middleware_error_from!( std::sync::MutexGuard<'_, std::cell::RefCell>>, > ); - +middleware_error_from!(std::sync::PoisonError>); middleware_error_from!(bigdecimal::ParseBigDecimalError); middleware_error_from!(std::env::VarError); middleware_error_from!(reqwest::header::InvalidHeaderValue); diff --git a/src/models.rs b/src/models.rs index a0ace6c4..f85369f8 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,9 +1,9 @@ #![allow(proc_macro_derive_resolution_fallback)] +use super::schema::associated_accounts; use super::schema::channel_identifiers; use super::schema::contract_calls; use super::schema::contract_identifiers; -use super::schema::associated_accounts; use super::schema::key_blocks; use super::schema::key_blocks::dsl::*; use super::schema::micro_blocks; @@ -26,6 +26,7 @@ use serde_json::Number; use std::collections::HashMap; use std::fmt; use std::str::FromStr; +use std::sync::{Arc, Condvar, Mutex}; use middleware_result::{MiddlewareError, MiddlewareResult}; use node::Node; @@ -440,13 +441,9 @@ pub struct Rate { } impl Transaction { - - pub fn load_for_id(conn: &PgConnection, _id: i32) -> MiddlewareResult - { + pub fn load_for_id(conn: &PgConnection, _id: i32) -> MiddlewareResult { use super::schema::transactions::dsl::*; - Ok(transactions.filter(id.eq(_id)) - .limit(1) - .first(conn)?) + Ok(transactions.filter(id.eq(_id)).limit(1).first(conn)?) } pub fn load_at_hash(conn: &PgConnection, _hash: &String) -> Option { @@ -674,34 +671,40 @@ impl InsertableAssociatedAccount { Ok(generated_ids[0]) } - pub fn from_transaction(connection: &PgConnection, transaction: &serde_json::Value, - _height: i64, _transaction_id: i32) - -> MiddlewareResult> - { + pub fn from_transaction( + connection: &PgConnection, + transaction: &serde_json::Value, + _height: i64, + _transaction_id: i32, + ) -> MiddlewareResult> { let name_hashes = get_name_hashes(&transaction)?; if name_hashes.len() == 0 { - return Ok(vec!()); + return Ok(vec![]); } - let mut result = vec!(); + let mut result = vec![]; for _name_hash in name_hashes { - if let Some(n) = Name::lookup_account_for_height_and_hash(connection, _height, &_name_hash)? { + if let Some(n) = + Name::lookup_account_for_height_and_hash(connection, _height, &_name_hash)? + { result.push(InsertableAssociatedAccount { transaction_id: _transaction_id, name_hash: _name_hash, - aeternity_id: n }); + aeternity_id: n, + }); } } - Ok(vec!()) + Ok(vec![]) } } pub fn get_name_hashes(v: &serde_json::Value) -> MiddlewareResult> { use regex::Regex; lazy_static! { - static ref NAME_REGEX: Regex = Regex::new( - "(nm_[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{38,60})").unwrap(); + static ref NAME_REGEX: Regex = + Regex::new("(nm_[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{38,60})") + .unwrap(); }; - let mut result = vec!(); + let mut result = vec![]; for capture in NAME_REGEX.captures_iter(serde_json::to_string(v)?.as_str()) { let s: String = String::from(&capture[0]); result.push(s); @@ -712,12 +715,20 @@ pub fn get_name_hashes(v: &serde_json::Value) -> MiddlewareResult> { #[test] fn test_get_name_hashes() { let v = serde_json::from_str(r#"{"fee": 16840000000000, "type": "SpendTx", "nonce": 130, "amount": 743000000000000000, "payload": "ba_Xfbg4g==", "version": 1, "sender_id": "ak_2AVeRypSdS4ZosdKWW1C4avWU4eeC2Yq7oP7guBGy8jkxdYVUy", "recipient_id": "nm_2U1TRRMfS218aNQ85qvs4RbrAofE15vQewdJi4awTTzSiFczKR"}"#).unwrap(); - assert_eq!(Ok(vec!(String::from("nm_2U1TRRMfS218aNQ85qvs4RbrAofE15vQewdJi4awTTzSiFczKR"))), - get_name_hashes(&v)); + assert_eq!( + Ok(vec!(String::from( + "nm_2U1TRRMfS218aNQ85qvs4RbrAofE15vQewdJi4awTTzSiFczKR" + ))), + get_name_hashes(&v) + ); let v = serde_json::from_str(r#"{"fee": 16840000000000, "type": "SpendTx", "nonce": 130, "amount": 743000000000000000, "payload": "ba_Xfbg4g==", "version": 1, "sender_id": "nm_2U1TRRMfS218aNQ85qvs4RbrAofE15vQewdJi4awTTzSiFczKR", "recipient_id": "nm_2U1TRRMfS218aNQ85qvs4RbrAofE15vQewdJi4awTTzSiFczKR"}"#).unwrap(); - assert_eq!(Ok(vec!(String::from("nm_2U1TRRMfS218aNQ85qvs4RbrAofE15vQewdJi4awTTzSiFczKR"), - String::from("nm_2U1TRRMfS218aNQ85qvs4RbrAofE15vQewdJi4awTTzSiFczKR"))), - get_name_hashes(&v)); + assert_eq!( + Ok(vec!( + String::from("nm_2U1TRRMfS218aNQ85qvs4RbrAofE15vQewdJi4awTTzSiFczKR"), + String::from("nm_2U1TRRMfS218aNQ85qvs4RbrAofE15vQewdJi4awTTzSiFczKR") + )), + get_name_hashes(&v) + ); } pub fn count_at_height( @@ -896,9 +907,10 @@ impl InsertableName { _owner: &str, _transaction_id: i32, ) -> MiddlewareResult { - let _auction_end_height = _created_at_height + crate::hashing::get_name_auction_length(_name)? as i64; + let _auction_end_height = + _created_at_height + crate::hashing::get_name_auction_length(_name)? as i64; Ok(InsertableName { - name: _name.to_string(), + name: _name.to_string().to_lowercase(), name_hash: _name_hash.to_string(), tx_hash: _tx_hash.to_string(), created_at_height: _created_at_height, @@ -910,7 +922,10 @@ impl InsertableName { }) } - pub fn new_from_transaction(tx_id: i32, transaction: &JsonTransaction) -> MiddlewareResult> { + pub fn new_from_transaction( + tx_id: i32, + transaction: &JsonTransaction, + ) -> MiddlewareResult> { let ttype = transaction.tx["type"].as_str()?; if ttype != "NameClaimTx" { return Ok(None); @@ -918,7 +933,8 @@ impl InsertableName { let name_salt_num: serde_json::Number = serde::de::Deserialize::deserialize(transaction.tx["name_salt"].to_owned())?; let name_salt = name_salt_num.to_string(); - if name_salt.eq(&String::from("0")) { // it's a bid. We don't store them + if name_salt.eq(&String::from("0")) { + // it's a bid. We don't store them return Ok(None); } let _name = transaction.tx["name"].as_str()?; @@ -947,6 +963,11 @@ impl InsertableName { } } +lazy_static! { + pub static ref NAME_CONDVAR: Arc<(Mutex, Condvar)> = + { Arc::new((Mutex::new(true), Condvar::new())) }; +} + #[derive(AsChangeset, Clone, Identifiable, Queryable, QueryableByName, Deserialize, Serialize)] #[table_name = "names"] pub struct Name { @@ -956,7 +977,7 @@ pub struct Name { pub name_hash: String, pub tx_hash: String, pub created_at_height: i64, - pub auction_end_height: i64, + pub auction_end_height: i64, pub owner: String, pub expires_at: i64, pub pointers: Option, @@ -965,9 +986,11 @@ pub struct Name { } impl Name { - pub fn get_for_height_and_name(connection: &PgConnection, _height: i64, _name: &String) -> - MiddlewareResult> - { + pub fn get_for_height_and_name( + connection: &PgConnection, + _height: i64, + _name: &String, + ) -> MiddlewareResult> { use schema::names::dsl::*; let result = names .filter(created_at_height.le(_height)) @@ -981,9 +1004,20 @@ impl Name { } } - pub fn get_for_height_and_hash(connection: &PgConnection, _height: i64, _hash: &String) -> - MiddlewareResult> - { + pub fn get_for_hash(connection: &PgConnection, _name_hash: &String) -> MiddlewareResult { + use schema::names::dsl::*; + Ok(names + .filter(name_hash.eq(_name_hash)) + .then_order_by(id.desc()) + .limit(1) + .first::(connection)?) + } + + pub fn get_for_height_and_hash( + connection: &PgConnection, + _height: i64, + _hash: &String, + ) -> MiddlewareResult> { use schema::names::dsl::*; let result = names .filter(created_at_height.le(_height)) @@ -997,20 +1031,62 @@ impl Name { } } - pub fn lookup_account_for_height_and_hash(connection: &PgConnection, _height: i64, _hash: &String) -> - MiddlewareResult> - { + pub fn lookup_account_for_height_and_hash( + connection: &PgConnection, + _height: i64, + _hash: &String, + ) -> MiddlewareResult> { if let Some(_info) = Self::get_for_height_and_name(connection, _height, _hash)? { if let Some(_pointers) = _info.pointers { for entry in _pointers.as_array()? { if let Some(_key) = entry["key"].as_str() { - return Ok(Some(String::from(entry["id"].as_str()?))) + return Ok(Some(String::from(entry["id"].as_str()?))); } } } } Ok(None) } + + pub fn generate_event() -> MiddlewareResult<()> { + let arc = &*NAME_CONDVAR; + let (lock, cvar) = &**arc; + let mut changed = lock.lock()?; + *changed = true; + cvar.notify_one(); + Ok(()) + } + + pub fn wait_for_event() -> MiddlewareResult<()> { + let arc = &*NAME_CONDVAR; + let (lock, cvar) = &*(*arc); + let mut changed = lock.lock()?; + while !*changed { + changed = cvar.wait(changed)?; + } + *changed = false; + Ok(()) + } +} + +#[test] +fn test_name_events() { + let arc = &*NAME_CONDVAR; + let (lock, _) = &*(*arc); + assert_eq!(*(lock.lock().unwrap()), true); + Name::wait_for_event().unwrap(); // starts ready + assert_eq!(*(lock.lock().unwrap()), false); + let handle = std::thread::spawn(|| { + Name::wait_for_event().unwrap(); + let arc = &*NAME_CONDVAR; + let (lock, _) = &*(*arc); + assert_eq!(*(lock.lock().unwrap()), false); + }); + assert_eq!(*(lock.lock().unwrap()), false); + Name::generate_event().unwrap(); + std::thread::sleep_ms(300); // random amount, should be enough + assert_eq!(*(lock.lock().unwrap()), false); + handle.join().unwrap(); // check thread has terminated. } #[derive(AsChangeset, Queryable, QueryableByName, Serialize, Clone, Debug)] @@ -1027,9 +1103,9 @@ pub struct NameAuctionEntry { impl NameAuctionEntry { pub fn load_for_name(connection: &PgConnection, _name: String) -> MiddlewareResult { use schema::name_auction_entries::dsl::*; - Ok(name_auction_entries. - filter(name.eq(_name)) - .first::(connection)?) + Ok(name_auction_entries + .filter(name.eq(_name)) + .first::(connection)?) } } @@ -1046,9 +1122,17 @@ impl Name { Some(_names.pop()?) } - pub fn owner_at_height(connection: &PgConnection, _name: &str, _height: i64) -> - MiddlewareResult - { + pub fn load_for_name(connection: &PgConnection, _name: &str) -> Option { + let sql = format!("select * from names where name='{}'", _name); + let mut _names: Vec = sql_query(sql).load(connection).unwrap(); + Some(_names.pop()?) + } + + pub fn owner_at_height( + connection: &PgConnection, + _name: &str, + _height: i64, + ) -> MiddlewareResult { use schema::names::dsl::*; let result = names .filter(name.eq(_name)) @@ -1058,10 +1142,13 @@ impl Name { Ok(result) } - pub fn reverse_name_at_height(_connection: &PgConnection, _account: &str, _height: i64) -> MiddlewareResult - { -// let _owner: Self = Self::owner_at_height(connection, _name, _height)?; -// let _name_hash: String = String::from_utf8(crate::hashing::get_name_hash(_name))?; + pub fn reverse_name_at_height( + _connection: &PgConnection, + _account: &str, + _height: i64, + ) -> MiddlewareResult { + // let _owner: Self = Self::owner_at_height(connection, _name, _height)?; + // let _name_hash: String = String::from_utf8(crate::hashing::get_name_hash(_name))?; let sql_connection = SQLCONNECTION.get()?; let rows = sql_connection.query( r#" @@ -1074,9 +1161,10 @@ WHERE ORDER BY t.block_height DESC DESC LIMIT 1 -"#, &[&String::from(_account), &_height])?; +"#, + &[&String::from(_account), &_height], + )?; Ok(rows.get(0).get(0)) - } pub fn update(&self, connection: &PgConnection) -> MiddlewareResult { @@ -1119,9 +1207,10 @@ DESC LIMIT 1 Ok(result) } - pub fn ending_auctions(connection: &PgConnection, _height: i64) -> - MiddlewareResult> - { + pub fn ending_auctions( + connection: &PgConnection, + _height: i64, + ) -> MiddlewareResult> { use schema::name_auction_entries::dsl::*; let result = name_auction_entries .filter(expiration.eq(_height)) @@ -1249,8 +1338,14 @@ impl InsertableContractCall { let client = reqwest::Client::new(); let arguments: serde_json::Value = match client.post(&full_url).json(¶ms).send() { Ok(mut data) => { - let output = data.text().unwrap(); - serde_json::from_str(&output)? + let output = data.text()?; + match serde_json::from_str(&output) { + Ok(x) => x, + Err(e) => { + error!("Couldn't parse JSON from compiler:\n{}\n{:?}", output, e); + serde_json::from_str(r#"{"error": "compiler error"#)? + } + } } Err(err) => { debug!("Error occurred while decoding call data: {:?}", err); diff --git a/src/server.rs b/src/server.rs index 4d85e2ee..bf2ac21d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1183,11 +1183,17 @@ struct AuctionInfo { } #[get("/names/auctions//info")] -fn info_for_auction(_state: State, name: String) -> Json { +fn info_for_auction(_state: State, name: String) -> + Result, Status> +{ let connection = &PGCONNECTION.get().unwrap(); let bids = crate::models::Name::bids_for_name(connection, name.clone()).unwrap(); - let info = crate::models::NameAuctionEntry::load_for_name(connection, name.clone()).unwrap(); - Json(AuctionInfo{ bids, info }) + if let Ok(info) = + crate::models::NameAuctionEntry::load_for_name(connection, name.clone()) { + Ok(Json(AuctionInfo{ bids, info })) + } else { + Err(rocket::http::Status::new(404, "Not found")) + } } #[get("/names/auctions/bids/?&")]