diff --git a/core/src/lib.rs b/core/src/lib.rs index b0d8bd0016..47315bc834 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -84,7 +84,7 @@ pub use crate::block::Block; pub use crate::client::Error::Database; pub use crate::client::{ AccountData, AssetClient, BlockChainClient, BlockChainTrait, ChainNotify, Client, ClientConfig, DatabaseClient, - EngineClient, EngineInfo, ExecuteClient, ImportBlock, MiningBlockChainClient, Shard, StateInfo, + EngineClient, EngineInfo, ExecuteClient, ImportBlock, MetadataInfo, MiningBlockChainClient, Shard, StateInfo, TestBlockChainClient, TextClient, }; pub use crate::consensus::{EngineType, TimeGapParams}; diff --git a/json/src/scheme/params.rs b/json/src/scheme/params.rs index 8eeb88ca25..4838b75d98 100644 --- a/json/src/scheme/params.rs +++ b/json/src/scheme/params.rs @@ -19,7 +19,7 @@ use ckey::NetworkId; use crate::uint::Uint; /// Scheme params. -#[derive(Debug, Default, PartialEq, Deserialize)] +#[derive(Debug, Default, PartialEq, Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct Params { /// Maximum size of extra data. diff --git a/json/src/uint.rs b/json/src/uint.rs index b41331c4ae..97b90a0f87 100644 --- a/json/src/uint.rs +++ b/json/src/uint.rs @@ -43,9 +43,15 @@ impl From for u32 { } } -impl From for Uint { - fn from(f: u64) -> Self { - Uint(f.into()) +impl From for u16 { + fn from(f: Uint) -> Self { + u64::from(f.0) as u16 + } +} + +impl From for u8 { + fn from(f: Uint) -> Self { + u64::from(f.0) as u8 } } @@ -56,9 +62,40 @@ impl From for usize { } } -impl From for u8 { - fn from(f: Uint) -> Self { - u64::from(f.0) as u8 +impl From for Uint { + fn from(f: u64) -> Self { + Uint(f.into()) + } +} + +impl From for Uint { + fn from(f: u32) -> Self { + Uint(f.into()) + } +} + +impl From for Uint { + fn from(f: u16) -> Self { + Uint(f.into()) + } +} + +impl From for Uint { + fn from(f: u8) -> Self { + Uint(f.into()) + } +} + +impl From for Uint { + fn from(f: usize) -> Self { + Uint(f.into()) + } +} + +// This impl is to support expressions like `0.into()` +impl From for Uint { + fn from(f: i32) -> Self { + Uint(f.into()) } } diff --git a/rpc/src/v1/impls/chain.rs b/rpc/src/v1/impls/chain.rs index fc0f6e1480..56d55898b6 100644 --- a/rpc/src/v1/impls/chain.rs +++ b/rpc/src/v1/impls/chain.rs @@ -17,8 +17,12 @@ use std::convert::{TryFrom, TryInto}; use std::sync::Arc; -use ccore::{AccountData, AssetClient, BlockId, EngineInfo, ExecuteClient, MiningBlockChainClient, Shard, TextClient}; +use ccore::{ + AccountData, AssetClient, BlockId, EngineInfo, ExecuteClient, MetadataInfo, MiningBlockChainClient, Shard, + TextClient, +}; use ccrypto::Blake; +use cjson::scheme::Params; use cjson::uint::Uint; use ckey::{public_to_address, NetworkId, PlatformAddress, Public}; use cstate::FindActionHandler; @@ -59,6 +63,7 @@ where + EngineInfo + FindActionHandler + TextClient + + MetadataInfo + 'static, { fn get_transaction(&self, transaction_hash: H256) -> Result> { @@ -294,6 +299,16 @@ where Ok(self.client.common_params(BlockId::Latest).unwrap().network_id()) } + fn get_common_params(&self, block_number: Option) -> Result> { + let block_id = block_number.map(BlockId::Number).unwrap_or(BlockId::Latest); + Ok(self.client.common_params(block_id).map(Params::from)) + } + + fn get_term_metadata(&self, block_number: Option) -> Result> { + let block_id = block_number.map(BlockId::Number).unwrap_or(BlockId::Latest); + Ok(self.client.metadata(block_id).map(|m| (m.last_term_finished_block_num(), m.current_term_id()))) + } + fn execute_transaction(&self, tx: UnsignedTransaction, sender: PlatformAddress) -> Result> { let sender_address = sender.try_address().map_err(errors::core)?; let action = Action::try_from(tx.action).map_err(errors::conversion)?; diff --git a/rpc/src/v1/traits/chain.rs b/rpc/src/v1/traits/chain.rs index 626b721f27..b164bc1c8b 100644 --- a/rpc/src/v1/traits/chain.rs +++ b/rpc/src/v1/traits/chain.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +use cjson::scheme::Params; use cjson::uint::Uint; use ckey::{NetworkId, PlatformAddress, Public}; use ctypes::{BlockNumber, ShardId}; @@ -136,6 +137,14 @@ build_rpc_trait! { # [rpc(name = "chain_getNetworkId")] fn get_network_id(&self) -> Result; + /// Return common params at given block number + #[rpc(name = "chain_getCommonParams")] + fn get_common_params(&self, Option) -> Result>; + + /// Return the current term id at given block number + #[rpc(name = "chain_getTermMetadata")] + fn get_term_metadata(&self, Option) -> Result>; + /// Execute Transactions # [rpc(name = "chain_executeTransaction")] fn execute_transaction(&self, UnsignedTransaction, PlatformAddress) -> Result>; diff --git a/spec/JSON-RPC.md b/spec/JSON-RPC.md index a6d2729477..fd33ef4b51 100644 --- a/spec/JSON-RPC.md +++ b/spec/JSON-RPC.md @@ -241,6 +241,40 @@ When `Transaction` is included in any response, there will be an additional fiel ## Signature `H520` for ECDSA signature | `H512` for Schnorr signature +## CommonParams + + - maxExtraDataSize: `U64` + - maxAssetSchemeMetadataSize: `U64` + - maxTransferMetadataSize: `U64` + - maxTextContentSize: `U64` + - networkID: `string` + - minPayCost: `U64` + - minSetRegularKeyCost: `U64` + - minCreateShardCost: `U64` + - minSetShardOwnersCost: `U64` + - minSetShardUsersCost: `U64` + - minWrapCccCost: `U64` + - minCustomCost: `U64` + - minStoreCost: `U64` + - minRemoveCost: `U64` + - minMintAssetCost: `U64` + - minTransferAssetCost: `U64` + - minChangeAssetSchemeCost: `U64` + - minIncreaseAssetSupplyCost: `U64` + - minComposeAssetCost: `U64` + - minDecomposeAssetCost: `U64` + - minUnwrapCccCost: `U64` + - maxBodySize: `U64` + - snapshotPeriod: `U64` + - term_seconds?: `U64` + - nomination_expiration?: `U64` + - custody_period?: `U64` + - release_period?: `U64` + - max_num_of_validators?: `U64` + - min_num_of_validators?: `U64` + - delegation_threshold?: `U64` + - min_deposit?: `U64` + # Error codes | Code | Message | Description | @@ -300,6 +334,8 @@ When `Transaction` is included in any response, there will be an additional fiel * [chain_getShardUsers](#chain_getshardusers) * [chain_getMiningReward](#chain_getminingreward) * [chain_getMinTransactionFee](#chain_getmintransactionfee) + * [chain_getCommonParams](#chain_getcommonparams) + * [chain_getTermMetadata](#chain_gettermmetadata) * [chain_executeTransaction](#chain_executetransaction) * [chain_executeVM](#chain_executevm) * [chain_getNetworkId](#chain_getnetworkid) @@ -1341,6 +1377,95 @@ Errors: `Invalid Params` [Back to **List of methods**](#list-of-methods) +# chain_getCommonParams +Gets the common parameters. +It returns null if the block number parameter is larger than the current best block. + +### Params + 1. block number - `number` | `null` + +### Returns +`CommonParams` | `null` + +Errors: `Invalid Params` + +### Request Example +``` + curl \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc": "2.0", "method": "chain_getCommonParams", "params": [3], "id": 7}' \ + localhost:8080 +``` + +### Response Example +``` +{ + "jsonrpc":"2.0", + "result":{ + "maxExtraDataSize":"0x20", + "maxAssetSchemeMetadataSize":"0x0400", + "maxTransferMetadataSize":"0x0100", + "maxTextContentSize":"0x0200", + "networkID":"tc", + "minPayCost":10, + "minSetRegularKeyCost":10, + "minCreateShardCost":10, + "minSetShardOwnersCost":10, + "minSetShardUsersCost":10, + "minWrapCccCost":10, + "minCustomCost":10, + "minStoreCost":10, + "minRemoveCost":10, + "minMintAssetCost":10, + "minTransferAssetCost":10, + "minChangeAssetSchemeCost":10, + "minIncreaseAssetSupplyCost":10, + "minComposeAssetCost":10, + "minDecomposeAssetCost":10, + "minUnwrapCccCost":10, + "maxBodySize":4194304, + "snapshotPeriod":16384 + }, + "id":7 +} +``` + +[Back to **List of methods**](#list-of-methods) + +# chain_getTermMetadata +Gets the term metadata. +It returns null if the block number parameter is larger than the current best block. + +### Params + 1. block number - `number` | `null` + +### Returns +`[number, number]` | `null` + +- The first item is the last block number that the term is closed. +- The second item is the current term id. + +Errors: `Invalid Params` + +### Request Example +``` + curl \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc": "2.0", "method": "chain_getTermMetadata", "params": [53], "id": 7}' \ + localhost:8080 +``` + +### Response Example +``` +{ + "jsonrpc":"2.0", + "result":[43,4], + "id":7 +} +``` + +[Back to **List of methods**](#list-of-methods) + ## chain_executeTransaction Executes the transactions and returns whether the execution is successful. diff --git a/state/src/item/metadata.rs b/state/src/item/metadata.rs index c697261704..25fa163cab 100644 --- a/state/src/item/metadata.rs +++ b/state/src/item/metadata.rs @@ -102,6 +102,10 @@ impl Metadata { pub fn last_term_finished_block_num(&self) -> u64 { self.term.last_term_finished_block_num } + + pub fn current_term_id(&self) -> u64 { + self.term.current_term_id + } } impl Default for Metadata { diff --git a/test/src/e2e/changeParams.test.ts b/test/src/e2e/changeParams.test.ts index 43a7c415ad..8970fa3a0f 100644 --- a/test/src/e2e/changeParams.test.ts +++ b/test/src/e2e/changeParams.test.ts @@ -15,7 +15,7 @@ // along with this program. If not, see . import { expect } from "chai"; -import { H256, PlatformAddress } from "codechain-primitives/lib"; +import { H256, PlatformAddress, U64 } from "codechain-primitives/lib"; import { blake256 } from "codechain-sdk/lib/utils"; import "mocha"; import { @@ -108,6 +108,20 @@ describe("ChangeParams", function() { } catch (err) { expect(err.message).contains("Too Low Fee"); } + + const params = await node.sdk.rpc.sendRpcRequest( + "chain_getCommonParams", + [null] + ); + expect(U64.ensure(params.minPayCost)).to.be.deep.equal(new U64(11)); + }); + + it("should keep default common params value", async function() { + const params = await node.sdk.rpc.sendRpcRequest( + "chain_getCommonParams", + [null] + ); + expect(U64.ensure(params.minPayCost)).to.be.deep.equal(new U64(10)); }); it("the parameter is applied from the next block", async function() { diff --git a/test/src/e2e/termChange.test.ts b/test/src/e2e/termChange.test.ts new file mode 100644 index 0000000000..4f90ac3d1b --- /dev/null +++ b/test/src/e2e/termChange.test.ts @@ -0,0 +1,170 @@ +// Copyright 2019 Kodebox, Inc. +// This file is part of CodeChain. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import { expect } from "chai"; +import { blake256 } from "codechain-sdk/lib/utils"; +import "mocha"; +import { + aliceAddress, + aliceSecret, + carolSecret, + faucetAddress, + faucetSecret, + stakeActionHandlerId, + validator0Address +} from "../helper/constants"; +import CodeChain from "../helper/spawn"; + +const RLP = require("rlp"); + +describe("ChangeParams", function() { + const chain = `${__dirname}/../scheme/solo-block-reward-50.json`; + let node: CodeChain; + + beforeEach(async function() { + node = new CodeChain({ + chain, + argv: ["--author", validator0Address.toString(), "--force-sealing"] + }); + await node.start(); + + const tx = await node.sendPayTx({ + fee: 10, + quantity: 100_000, + recipient: aliceAddress + }); + expect(await node.sdk.rpc.chain.containsTransaction(tx.hash())).be.true; + }); + + async function changeTermSeconds(metadataSeq: number, termSeconds: number) { + const newParams = [ + 0x20, // maxExtraDataSize + 0x0400, // maxAssetSchemeMetadataSize + 0x0100, // maxTransferMetadataSize + 0x0200, // maxTextContentSize + "tc", // networkID + 10, // minPayCost + 10, // minSetRegularKeyCost + 10, // minCreateShardCost + 10, // minSetShardOwnersCost + 10, // minSetShardUsersCost + 10, // minWrapCccCost + 10, // minCustomCost + 10, // minStoreCost + 10, // minRemoveCost + 10, // minMintAssetCost + 10, // minTransferAssetCost + 10, // minChangeAssetSchemeCost + 10, // minIncreaseAssetSupplyCost + 10, // minComposeAssetCost + 10, // minDecomposeAssetCost + 10, // minUnwrapCccCost + 4194304, // maxBodySize + 16384, // snapshotPeriod + termSeconds, // termSeconds + 0, // nominationExpiration + 0, // custodyPeriod + 0, // releasePeriod + 0, // maxNumOfValidators + 0, // minNumOfValidators + 0, // delegationThreshold + 0 // minDeposit + ]; + const changeParams: (number | string | (number | string)[])[] = [ + 0xff, + metadataSeq, + newParams + ]; + const message = blake256(RLP.encode(changeParams).toString("hex")); + changeParams.push(`0x${node.sdk.util.signEcdsa(message, aliceSecret)}`); + changeParams.push(`0x${node.sdk.util.signEcdsa(message, carolSecret)}`); + + { + const hash = await node.sdk.rpc.chain.sendSignedTransaction( + node.sdk.core + .createCustomTransaction({ + handlerId: stakeActionHandlerId, + bytes: RLP.encode(changeParams) + }) + .sign({ + secret: faucetSecret, + seq: await node.sdk.rpc.chain.getSeq(faucetAddress), + fee: 10 + }) + ); + expect(await node.sdk.rpc.chain.containsTransaction(hash)).be.true; + } + } + + it("initial term metadata", async function() { + const params = await node.sdk.rpc.sendRpcRequest( + "chain_getTermMetadata", + [null] + ); + expect(params).to.be.deep.equals([0, 0]); + }); + + async function waitForTermPeriodChange(termSeconds: number) { + const lastBlockNumber = await node.sdk.rpc.chain.getBestBlockNumber(); + const lastBlock = (await node.sdk.rpc.chain.getBlock(lastBlockNumber))!; + + let previousTs = lastBlock.timestamp; + for (let count = 0; count < 20; count++) { + await node.sdk.rpc.devel.startSealing(); + const blockNumber = await node.sdk.rpc.chain.getBestBlockNumber(); + const block = (await node.sdk.rpc.chain.getBlock(blockNumber))!; + + const currentTs = block.timestamp; + const previousTermPeriod = Math.floor(previousTs / termSeconds); + const currentTermPeriod = Math.floor(currentTs / termSeconds); + if (previousTermPeriod !== currentTermPeriod) { + return blockNumber; + } + previousTs = currentTs; + await new Promise(resolve => setTimeout(resolve, 1000)); + } + + throw new Error("Timeout on waiting term period change"); + } + + it("can turn on term change", async function() { + const TERM_SECONDS = 3; + await changeTermSeconds(0, TERM_SECONDS); + + const blockNumber1 = await waitForTermPeriodChange(TERM_SECONDS); + + const params1 = await node.sdk.rpc.sendRpcRequest( + "chain_getTermMetadata", + [blockNumber1] + ); + expect(params1).to.be.deep.equals([blockNumber1, 1]); + + const blockNumber2 = await waitForTermPeriodChange(TERM_SECONDS); + + const params2 = await node.sdk.rpc.sendRpcRequest( + "chain_getTermMetadata", + [blockNumber2] + ); + expect(params2).to.be.deep.equals([blockNumber2, 2]); + }); + + afterEach(async function() { + if (this.currentTest!.state === "failed") { + node.testFailed(this.currentTest!.fullTitle()); + } + await node.clean(); + }); +}); diff --git a/types/src/common_params.rs b/types/src/common_params.rs index d4fe2b3919..0775f1f1d1 100644 --- a/types/src/common_params.rs +++ b/types/src/common_params.rs @@ -206,6 +206,48 @@ impl From for CommonParams { } } +impl From for Params { + fn from(p: CommonParams) -> Params { + let mut result = Params { + max_extra_data_size: p.max_extra_data_size().into(), + max_asset_scheme_metadata_size: p.max_asset_scheme_metadata_size().into(), + max_transfer_metadata_size: p.max_transfer_metadata_size().into(), + max_text_content_size: p.max_text_content_size().into(), + network_id: p.network_id(), + min_pay_cost: p.min_pay_transaction_cost().into(), + min_set_regular_key_cost: p.min_set_regular_key_transaction_cost().into(), + min_create_shard_cost: p.min_create_shard_transaction_cost().into(), + min_set_shard_owners_cost: p.min_set_shard_owners_transaction_cost().into(), + min_set_shard_users_cost: p.min_set_shard_users_transaction_cost().into(), + min_wrap_ccc_cost: p.min_wrap_ccc_transaction_cost().into(), + min_custom_cost: p.min_custom_transaction_cost().into(), + min_store_cost: p.min_store_transaction_cost().into(), + min_remove_cost: p.min_remove_transaction_cost().into(), + min_mint_asset_cost: p.min_asset_mint_cost().into(), + min_transfer_asset_cost: p.min_asset_transfer_cost().into(), + min_change_asset_scheme_cost: p.min_asset_scheme_change_cost().into(), + min_increase_asset_supply_cost: p.min_asset_supply_increase_cost().into(), + min_compose_asset_cost: p.min_asset_compose_cost().into(), + min_decompose_asset_cost: p.min_asset_decompose_cost().into(), + min_unwrap_ccc_cost: p.min_asset_unwrap_ccc_cost().into(), + max_body_size: p.max_body_size().into(), + snapshot_period: p.snapshot_period().into(), + ..Default::default() + }; + if p.size == 31 { + result.term_seconds = Some(p.term_seconds().into()); + result.nomination_expiration = Some(p.nomination_expiration().into()); + result.custody_period = Some(p.custody_period().into()); + result.release_period = Some(p.release_period().into()); + result.max_num_of_validators = Some(p.max_num_of_validators().into()); + result.min_num_of_validators = Some(p.min_num_of_validators().into()); + result.delegation_threshold = Some(p.delegation_threshold().into()); + result.min_deposit = Some(p.min_deposit().into()); + } + result + } +} + impl Encodable for CommonParams { fn rlp_append(&self, s: &mut RlpStream) { const VALID_SIZE: &[usize] = &[23, 31]; @@ -427,7 +469,8 @@ mod tests { "snapshotPeriod": 16384 }"#; - let deserialized = CommonParams::from(serde_json::from_str::(s).unwrap()); + let params = serde_json::from_str::(s).unwrap(); + let deserialized = CommonParams::from(params.clone()); assert_eq!(deserialized.max_extra_data_size, 0x20); assert_eq!(deserialized.max_asset_scheme_metadata_size, 0x0400); assert_eq!(deserialized.max_transfer_metadata_size, 0x0100); @@ -459,6 +502,8 @@ mod tests { assert_eq!(deserialized.min_num_of_validators, 0); assert_eq!(deserialized.delegation_threshold, 0); assert_eq!(deserialized.min_deposit, 0); + + assert_eq!(params, deserialized.into()); } #[test] @@ -491,7 +536,8 @@ mod tests { "termSeconds": 3600 }"#; - let deserialized = CommonParams::from(serde_json::from_str::(s).unwrap()); + let params = serde_json::from_str::(s).unwrap(); + let deserialized = CommonParams::from(params.clone()); assert_eq!(deserialized.max_extra_data_size, 0x20); assert_eq!(deserialized.max_asset_scheme_metadata_size, 0x0400); assert_eq!(deserialized.max_transfer_metadata_size, 0x0100); @@ -523,6 +569,21 @@ mod tests { assert_eq!(deserialized.min_num_of_validators, 0); assert_eq!(deserialized.delegation_threshold, 0); assert_eq!(deserialized.min_deposit, 0); + + assert_eq!( + Params { + nomination_expiration: Some(0.into()), + custody_period: Some(0.into()), + release_period: Some(0.into()), + max_num_of_validators: Some(0.into()), + min_num_of_validators: Some(0.into()), + delegation_threshold: Some(0.into()), + min_deposit: Some(0.into()), + ..params + }, + deserialized.into(), + "Convert back will fill default values" + ); } #[test] @@ -561,8 +622,8 @@ mod tests { "delegationThreshold": 31, "minDeposit": 32 }"#; - - let deserialized = CommonParams::from(serde_json::from_str::(s).unwrap()); + let params = serde_json::from_str::(s).unwrap(); + let deserialized = CommonParams::from(params.clone()); assert_eq!(deserialized.max_extra_data_size, 0x20); assert_eq!(deserialized.max_asset_scheme_metadata_size, 0x0400); assert_eq!(deserialized.max_transfer_metadata_size, 0x0100); @@ -594,5 +655,7 @@ mod tests { assert_eq!(deserialized.min_num_of_validators, 30); assert_eq!(deserialized.delegation_threshold, 31); assert_eq!(deserialized.min_deposit, 32); + + assert_eq!(params, deserialized.into()); } }