diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 229309a..18de892 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -16,7 +16,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 5.0.x + dotnet-version: 7.0.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/.github/workflows/integration-testing.yml b/.github/workflows/integration-testing.yml index 2b0b93d..a8b62be 100644 --- a/.github/workflows/integration-testing.yml +++ b/.github/workflows/integration-testing.yml @@ -23,12 +23,13 @@ jobs: - 11101:11101 - 14101:14101 - 18101:18101 + - 25101:25101 steps: - uses: actions/checkout@v2 - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 5.0.x + dotnet-version: 7.0.x - name: Obtain faucet secret key from container run: docker exec -t casper-nctl cat /home/casper/casper-node/utils/nctl/assets/net-1/faucet/secret_key.pem > Casper.Network.SDK.Test/TestData/faucetact.pem - name: Restore dependencies @@ -36,4 +37,4 @@ jobs: - name: Build run: dotnet build --no-restore - name: Test - run: dotnet test --no-build --verbosity normal --settings Casper.Network.SDK.Test/test.runsettings --filter="TestCategory=NCTL" \ No newline at end of file + run: dotnet test --no-build --verbosity normal --settings Casper.Network.SDK.Test/test.runsettings --filter="TestCategory=NCTL" diff --git a/.github/workflows/nuget-publish.yml b/.github/workflows/nuget-publish.yml index 0829185..41533ec 100644 --- a/.github/workflows/nuget-publish.yml +++ b/.github/workflows/nuget-publish.yml @@ -14,7 +14,7 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v1 with: - dotnet-version: 5.0.x + dotnet-version: 7.0.x - name: publish on version change id: publish_nuget diff --git a/.github/workflows/push-to-github-registry.yml b/.github/workflows/push-to-github-registry.yml index 1a597b3..fa33398 100644 --- a/.github/workflows/push-to-github-registry.yml +++ b/.github/workflows/push-to-github-registry.yml @@ -13,7 +13,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 5.0.x + dotnet-version: 7.0.x - name: Create nuget package run: dotnet pack --configuration Release -o out - name: Publish Nuget to GitHub registry diff --git a/CHANGELOG.md b/CHANGELOG.md index b17de77..b73a860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,23 @@ All notable changes to this project will be documented in this file. The format [comment]: <> (Fixed: any bug fixes) [comment]: <> (Security: in case of vulnerabilities) +## [2.0.0] + +### Added +* Added client method for new `query_balance` RPC endpoint. +* Added client method for new `speculative_exec` feature in casper node v1.5.0. +* Added client method for new `info_get_chainspec` RPC endpoint. +* Added client method for new `chain_get_era_summary` RPC endpoint. +* Added support for `block_height` parameter in `query_global_state` RPC endpoint. +* Added new key types: `chainspec-registry-`, `system-contract-registry-`, `checksum-registry-`, `era-summary`, `unbond-`. +* Added support for new key prefix 'contract-package-'. +* Added new fields related to node sync in `info_get_status` RPC response. +* Added support optional fields in `info_get_deploy` RPC response. +* Added `lock_status` field to `ContractPackageStatus`. + +### Changed +* The SDK now builds with .NET 7 Framework. +* ## [1.1.2] ### Added @@ -39,6 +56,7 @@ All notable changes to this project will be documented in this file. The format ### Added * Initial release of Casper .NET SDK. +[2.0.0]: https://github.com/make-software/casper-net-sdk/releases/tag/v2.0.0 [1.1.2]: https://github.com/make-software/casper-net-sdk/releases/tag/v1.1.2 [1.1.1]: https://github.com/make-software/casper-net-sdk/releases/tag/v1.1.1 [1.1.0]: https://github.com/make-software/casper-net-sdk/releases/tag/v1.1.0 diff --git a/Casper.Network.SDK.Test/Casper.Network.SDK.Test.csproj b/Casper.Network.SDK.Test/Casper.Network.SDK.Test.csproj index c6bdb1d..e14e06f 100644 --- a/Casper.Network.SDK.Test/Casper.Network.SDK.Test.csproj +++ b/Casper.Network.SDK.Test/Casper.Network.SDK.Test.csproj @@ -1,7 +1,7 @@ - net5.0 + net7.0 false @@ -9,10 +9,10 @@ - - - - + + + + diff --git a/Casper.Network.SDK.Test/GlobalStateKeyTest.cs b/Casper.Network.SDK.Test/GlobalStateKeyTest.cs index 5cacea1..6a9ed65 100644 --- a/Casper.Network.SDK.Test/GlobalStateKeyTest.cs +++ b/Casper.Network.SDK.Test/GlobalStateKeyTest.cs @@ -13,6 +13,8 @@ public class GlobalStateKeyTest public void AccountHashTest() { const string accountHash = "account-hash-0101010101010101010101010101010101010101010101010101010101010101"; + Assert.IsTrue(accountHash.StartsWith(AccountHashKey.KEYPREFIX)); + var key = GlobalStateKey.FromString(accountHash); Assert.IsNotNull(key); Assert.IsTrue(key is AccountHashKey); @@ -34,13 +36,15 @@ public void AccountHashFromPublicKeyTest() var key2 = new AccountHashKey(publicKey); Assert.IsNotNull(key2); - Assert.IsTrue(key2.ToString().StartsWith("account-hash-")); + Assert.IsTrue(key2.ToString().StartsWith(AccountHashKey.KEYPREFIX)); } [Test] public void HashKeyTest() { const string hashKey = "hash-0202020202020202020202020202020202020202020202020202020202020202"; + Assert.IsTrue(hashKey.StartsWith(HashKey.KEYPREFIX)); + var key = GlobalStateKey.FromString(hashKey); Assert.IsNotNull(key); Assert.IsTrue(key is HashKey); @@ -58,6 +62,8 @@ public void HashKeyTest() public void URefTest() { const string urefKey = "uref-0303030303030303030303030303030303030303030303030303030303030303-001"; + Assert.IsTrue(urefKey.StartsWith(URef.KEYPREFIX)); + var key = GlobalStateKey.FromString(urefKey); Assert.IsNotNull(key); Assert.IsTrue(key is URef); @@ -77,6 +83,8 @@ public void URefTest() public void TransferKeyTest() { const string transferKey = "transfer-0404040404040404040404040404040404040404040404040404040404040404"; + Assert.IsTrue(transferKey.StartsWith(TransferKey.KEYPREFIX)); + var key = GlobalStateKey.FromString(transferKey); Assert.IsNotNull(key); Assert.IsTrue(key is TransferKey); @@ -94,6 +102,8 @@ public void TransferKeyTest() public void DeployInfoKeyTest() { const string deployKey = "deploy-0505050505050505050505050505050505050505050505050505050505050505"; + Assert.IsTrue(deployKey.StartsWith(DeployInfoKey.KEYPREFIX)); + var key = GlobalStateKey.FromString(deployKey); Assert.IsNotNull(key); Assert.IsTrue(key is DeployInfoKey); @@ -111,6 +121,8 @@ public void DeployInfoKeyTest() public void EraInfoKeyTest() { const string eraInfoKey = "era-12345"; + Assert.IsTrue(eraInfoKey.StartsWith(EraInfoKey.KEYPREFIX)); + var key = GlobalStateKey.FromString(eraInfoKey); Assert.IsNotNull(key); Assert.IsTrue(key is EraInfoKey); @@ -131,6 +143,8 @@ public void EraInfoKeyTest() public void BalanceKeyTest() { const string balanceKey = "balance-0707070707070707070707070707070707070707070707070707070707070707"; + Assert.IsTrue(balanceKey.StartsWith(BalanceKey.KEYPREFIX)); + var key = GlobalStateKey.FromString(balanceKey); Assert.IsNotNull(key); Assert.IsTrue(key is BalanceKey); @@ -148,6 +162,8 @@ public void BalanceKeyTest() public void BidKeyTest() { const string bidKey = "bid-0808080808080808080808080808080808080808080808080808080808080808"; + Assert.IsTrue(bidKey.StartsWith(BidKey.KEYPREFIX)); + var key = GlobalStateKey.FromString(bidKey); Assert.IsNotNull(key); Assert.IsTrue(key is BidKey); @@ -165,6 +181,8 @@ public void BidKeyTest() public void WithdrawKeyTest() { const string withdrawKey = "withdraw-0909090909090909090909090909090909090909090909090909090909090909"; + Assert.IsTrue(withdrawKey.StartsWith(WithdrawKey.KEYPREFIX)); + var key = GlobalStateKey.FromString(withdrawKey); Assert.IsNotNull(key); Assert.IsTrue(key is WithdrawKey); @@ -182,6 +200,8 @@ public void WithdrawKeyTest() public void DictionaryKeyTest() { const string dictionaryKey = "dictionary-1010101010101010101010101010101010101010101010101010101010101010"; + Assert.IsTrue(dictionaryKey.StartsWith(DictionaryKey.KEYPREFIX)); + var key = GlobalStateKey.FromString(dictionaryKey); Assert.IsNotNull(key); Assert.IsTrue(key is DictionaryKey); @@ -195,6 +215,112 @@ public void DictionaryKeyTest() Assert.IsTrue(key.GetBytes().SequenceEqual(bytes)); } + [Test] + public void SystemContractRegistryKeyTest() + { + const string systemContractKey = "system-contract-registry-0909090909090909090909090909090909090909090909090909090909090909"; + Assert.IsTrue(systemContractKey.StartsWith(SystemContractRegistryKey.KEYPREFIX)); + + var key = GlobalStateKey.FromString(systemContractKey); + Assert.IsNotNull(key); + Assert.IsTrue(key is SystemContractRegistryKey); + Assert.AreEqual(systemContractKey, key.ToString()); + + var bytes = Hex.Decode("0a0909090909090909090909090909090909090909090909090909090909090909"); + key = GlobalStateKey.FromBytes(bytes); + Assert.IsNotNull(key); + Assert.IsTrue(key is SystemContractRegistryKey); + Assert.AreEqual(systemContractKey, key.ToString()); + Assert.IsTrue(key.GetBytes().SequenceEqual(bytes)); + } + + [Test] + public void ErasummaryKeyTest() + { + const string eraSummaryKey = "era-summary-0909090909090909090909090909090909090909090909090909090909090909"; + Assert.IsTrue(eraSummaryKey.StartsWith(EraSummaryKey.KEYPREFIX)); + + var key = GlobalStateKey.FromString(eraSummaryKey); + Assert.IsNotNull(key); + Assert.IsTrue(key is EraSummaryKey); + Assert.AreEqual(eraSummaryKey, key.ToString()); + + var bytes = Hex.Decode("0b0909090909090909090909090909090909090909090909090909090909090909"); + key = GlobalStateKey.FromBytes(bytes); + Assert.IsNotNull(key); + Assert.IsTrue(key is EraSummaryKey); + Assert.AreEqual(eraSummaryKey, key.ToString()); + Assert.IsTrue(key.GetBytes().SequenceEqual(bytes)); + } + + [Test] + public void UnbondKeyTest() + { + const string unbondKey = "unbond-0909090909090909090909090909090909090909090909090909090909090909"; + Assert.IsTrue(unbondKey.StartsWith(UnbondKey.KEYPREFIX)); + + var key = GlobalStateKey.FromString(unbondKey); + Assert.IsNotNull(key); + Assert.IsTrue(key is UnbondKey); + Assert.AreEqual(unbondKey, key.ToString()); + + var bytes = Hex.Decode("0c0909090909090909090909090909090909090909090909090909090909090909"); + key = GlobalStateKey.FromBytes(bytes); + Assert.IsNotNull(key); + Assert.IsTrue(key is UnbondKey); + Assert.AreEqual(unbondKey, key.ToString()); + Assert.IsTrue(key.GetBytes().SequenceEqual(bytes)); + } + + [Test] + public void ChainspecRegistryKeyTest() + { + const string chainspecRegistryKey = "chainspec-registry-0909090909090909090909090909090909090909090909090909090909090909"; + Assert.IsTrue(chainspecRegistryKey.StartsWith(ChainspecRegistryKey.KEYPREFIX)); + + var key = GlobalStateKey.FromString(chainspecRegistryKey); + Assert.IsNotNull(key); + Assert.IsTrue(key is ChainspecRegistryKey); + Assert.AreEqual(chainspecRegistryKey, key.ToString()); + + var bytes = Hex.Decode("0d0909090909090909090909090909090909090909090909090909090909090909"); + key = GlobalStateKey.FromBytes(bytes); + Assert.IsNotNull(key); + Assert.IsTrue(key is ChainspecRegistryKey); + Assert.AreEqual(chainspecRegistryKey, key.ToString()); + Assert.IsTrue(key.GetBytes().SequenceEqual(bytes)); + } + + [Test] + public void ChecksumRegistryKeyTest() + { + const string checksumRegistryKey = "checksum-registry-0909090909090909090909090909090909090909090909090909090909090909"; + Assert.IsTrue(checksumRegistryKey.StartsWith(ChecksumRegistryKey.KEYPREFIX)); + + var key = GlobalStateKey.FromString(checksumRegistryKey); + Assert.IsNotNull(key); + Assert.IsTrue(key is ChecksumRegistryKey); + Assert.AreEqual(checksumRegistryKey, key.ToString()); + + var bytes = Hex.Decode("0e0909090909090909090909090909090909090909090909090909090909090909"); + key = GlobalStateKey.FromBytes(bytes); + Assert.IsNotNull(key); + Assert.IsTrue(key is ChecksumRegistryKey); + Assert.AreEqual(checksumRegistryKey, key.ToString()); + Assert.IsTrue(key.GetBytes().SequenceEqual(bytes)); + } + + [Test] + public void ContractsKeyTest() + { + const string contractPackageKey = + "contract-package-0909090909090909090909090909090909090909090909090909090909090909"; + + var key = GlobalStateKey.FromString(contractPackageKey); + Assert.IsNotNull(key); + Assert.IsTrue(key is HashKey); + } + [Test] public void InvalidPrefixTest() { @@ -275,7 +401,7 @@ public void InvalidPrefixTest() GlobalStateKey.FromString(invalidPrefixKey); }); Assert.IsNotNull(ex); - Assert.IsTrue(ex.Message.StartsWith("Key not valid. Unknown key prefix.")); + Assert.IsTrue(ex.Message.StartsWith("Key not valid. Unknown key prefix")); } [Test] @@ -332,4 +458,4 @@ public void HashKeyJsonDeserializeTest() Assert.IsTrue(ex.Message.Contains("checksum mismatch")); } } -} \ No newline at end of file +} diff --git a/Casper.Network.SDK.Test/NctlContractTest.cs b/Casper.Network.SDK.Test/NctlContractTest.cs index f5e4349..fb8e646 100644 --- a/Casper.Network.SDK.Test/NctlContractTest.cs +++ b/Casper.Network.SDK.Test/NctlContractTest.cs @@ -73,7 +73,8 @@ public async Task QueryContractKeysTest() var rpcResponse3 = await _client.QueryGlobalState(_contractPackageKey); var contractPackageInfo = rpcResponse3.Parse().StoredValue.ContractPackage; Assert.IsTrue(contractPackageInfo.Versions.Count > 0); - + Assert.AreEqual(LockStatus.Locked, contractPackageInfo.LockStatus); + var contractWasmKey = GlobalStateKey.FromString(contractInfo.ContractWasmHash); var rpcResponse4 = await _client.QueryGlobalState(contractWasmKey); var contractWasmInfo = rpcResponse4.Parse().StoredValue.ContractWasm; @@ -91,7 +92,7 @@ public async Task CallContractByNameTest() 150_000_000, _chainName, 1, //gasPrice=1 - 24*3_600_000); //ttl='1day' + 18*3_600_000); //ttl='18h' deploy.Sign(_faucetKey); var putResponse = await _client.PutDeploy(deploy); diff --git a/Casper.Network.SDK.Test/NctlGetXTest.cs b/Casper.Network.SDK.Test/NctlGetXTest.cs index 73fbeb1..44ba2ba 100644 --- a/Casper.Network.SDK.Test/NctlGetXTest.cs +++ b/Casper.Network.SDK.Test/NctlGetXTest.cs @@ -46,17 +46,56 @@ public async Task GetAccountTest() { try { - var response = await _client.GetAccountInfo(_faucetKey.PublicKey, 1); + var block = (await _client.GetBlock()).Parse().Block; + var blockHeight = (int) block.Header.Height; + var blockHash = block.Hash; + var stateRootHash = await _client.GetStateRootHash(blockHash); + + var response = await _client.GetAccountInfo(_faucetKey.PublicKey, blockHeight); var accountInfo = response.Parse(); Assert.IsNotEmpty(accountInfo.Account.AccountHash.ToString()); - var response2 = await _client.GetAccountBalance(_faucetKey.PublicKey); - var accountBalance = response2.Parse(); + var resp = await _client.GetAccountBalance(_faucetKey.PublicKey, stateRootHash); + var accountBalance = resp.Parse(); Assert.IsTrue(accountBalance.BalanceValue > 0); - var response3 = await _client.GetAccountBalance(accountInfo.Account.MainPurse); - var accountBalance2 = response3.Parse(); - Assert.AreEqual(accountBalance.BalanceValue, accountBalance2.BalanceValue); + resp = await _client.GetAccountBalance(accountInfo.Account.MainPurse, stateRootHash); + var accountBalanceCompare = resp.Parse(); + Assert.AreEqual(accountBalance.BalanceValue, accountBalanceCompare.BalanceValue); + + resp = await _client.GetAccountBalance(accountInfo.Account.MainPurse.ToString(), stateRootHash); + accountBalanceCompare = resp.Parse(); + Assert.AreEqual(accountBalance.BalanceValue, accountBalanceCompare.BalanceValue); + + resp = await _client.GetAccountBalance(new AccountHashKey(_faucetKey.PublicKey), stateRootHash); + accountBalanceCompare = resp.Parse(); + Assert.AreEqual(accountBalance.BalanceValue, accountBalanceCompare.BalanceValue); + + + resp = await _client.GetAccountBalance(_faucetKey.PublicKey, blockHeight); + accountBalanceCompare = resp.Parse(); + Assert.AreEqual(accountBalance.BalanceValue, accountBalanceCompare.BalanceValue); + + resp = await _client.GetAccountBalance(accountInfo.Account.MainPurse, blockHeight); + accountBalanceCompare = resp.Parse(); + Assert.AreEqual(accountBalance.BalanceValue, accountBalanceCompare.BalanceValue); + + resp = await _client.GetAccountBalance(new AccountHashKey(_faucetKey.PublicKey), blockHeight); + accountBalanceCompare = resp.Parse(); + Assert.AreEqual(accountBalance.BalanceValue, accountBalanceCompare.BalanceValue); + + + resp = await _client.GetAccountBalanceWithBlockHash(_faucetKey.PublicKey, blockHash); + accountBalanceCompare = resp.Parse(); + Assert.AreEqual(accountBalance.BalanceValue, accountBalanceCompare.BalanceValue); + + resp = await _client.GetAccountBalanceWithBlockHash(accountInfo.Account.MainPurse, blockHash); + accountBalanceCompare = resp.Parse(); + Assert.AreEqual(accountBalance.BalanceValue, accountBalanceCompare.BalanceValue); + + resp = await _client.GetAccountBalanceWithBlockHash(new AccountHashKey(_faucetKey.PublicKey), blockHash); + accountBalanceCompare = resp.Parse(); + Assert.AreEqual(accountBalance.BalanceValue, accountBalanceCompare.BalanceValue); } catch (RpcClientException e) { @@ -238,5 +277,23 @@ public async Task GetRpcSchemaTest() Assert.Fail(e.RpcError.Message); } } + + [Test] + public async Task GetChainspecTest() + { + try + { + var response = await _client.GetChainspec(); + Assert.IsNotNull(response); + + var result = response.Parse(); + Assert.IsNotNull(result.ChainspecBytes.ChainspecBytes); + Assert.IsNotNull(result.ChainspecBytes.ChainspecAsString); + } + catch (RpcClientException e) + { + Assert.Fail(e.RpcError.Message); + } + } } } diff --git a/Casper.Network.SDK.Test/NctlQueryGlobalStateTest.cs b/Casper.Network.SDK.Test/NctlQueryGlobalStateTest.cs index 799d144..6c96e3a 100644 --- a/Casper.Network.SDK.Test/NctlQueryGlobalStateTest.cs +++ b/Casper.Network.SDK.Test/NctlQueryGlobalStateTest.cs @@ -32,7 +32,15 @@ public async Task QueryURef() var clValue = rpcResponse.Parse().StoredValue.CLValue; Assert.IsNotNull(clValue); } - + + [Test] + public async Task QueryGlobalStateByHeight() + { + var rpcResponse = await _client.QueryGlobalState("era-summary-0000000000000000000000000000000000000000000000000000000000000000", 1); + var blockHeader = rpcResponse.Parse().BlockHeader; + Assert.AreEqual(1, blockHeader.Height); + } + [Test] public async Task QueryEraSummary() { diff --git a/Casper.Network.SDK.Test/NctlSpeculativeExecutionTest.cs b/Casper.Network.SDK.Test/NctlSpeculativeExecutionTest.cs new file mode 100644 index 0000000..58166e7 --- /dev/null +++ b/Casper.Network.SDK.Test/NctlSpeculativeExecutionTest.cs @@ -0,0 +1,64 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Casper.Network.SDK; +using Casper.Network.SDK.Types; +using Casper.Network.SDK.Utils; +using NUnit.Framework; + +namespace NetCasperTest +{ + [Category("NCTL"), NonParallelizable] + public class NctlSpeculativeExecutionTest + { + protected string _nodeAddress; + protected string _chainName = "casper-net-1"; + protected NetCasperClient _client; + protected KeyPair _faucetKey; + + private string _wasmFile = TestContext.CurrentContext.TestDirectory + + "/TestData/counter-define.wasm"; + + [SetUp] + public void Setup() + { + _nodeAddress = Environment.GetEnvironmentVariable("CASPERNETSDK_NODE_ADDRESS"); + Assert.IsNotNull(_nodeAddress, + "Please, set environment variable CASPERNETSDK_NODE_ADDRESS with a valid node url (with port)."); + _nodeAddress = _nodeAddress.Replace("11101", "25101"); + + _client = new NetCasperClient(_nodeAddress); + + var fkFilename = TestContext.CurrentContext.TestDirectory + + "/TestData/faucetact.pem"; + _faucetKey = KeyPair.FromPem(fkFilename); + Assert.IsNotNull(_faucetKey, $"Cannot read faucet key from '{fkFilename}"); + } + + [Test, Order(1)] + public async Task SpeculativeExecutionTest() + { + var wasmBytes = await File.ReadAllBytesAsync(_wasmFile); + + var deploy = DeployTemplates.ContractDeploy( + wasmBytes, + _faucetKey.PublicKey, + 50_000_000_000, + _chainName, + 1, //gasPrice=1 + 45011500); //ttl='12h 30m 11s 500ms' + deploy.Sign(_faucetKey); + + var rpcResponse = await _client.SpeceulativeExecution(deploy); + + var result = rpcResponse.Parse(); + Assert.IsNotNull(result.BlockHash); + Assert.IsNotNull(result.ExecutionResult); + Assert.IsTrue(result.ExecutionResult.Effect.Transforms.Count > 0); + } + } + + +} diff --git a/Casper.Network.SDK.Test/RpcResultTest.cs b/Casper.Network.SDK.Test/RpcResultTest.cs index 8d13711..1d42b19 100644 --- a/Casper.Network.SDK.Test/RpcResultTest.cs +++ b/Casper.Network.SDK.Test/RpcResultTest.cs @@ -117,5 +117,24 @@ public void RpcResultParseTest() Assert.AreEqual(result.Account.AccountHash.ToString(), result.Account.AssociatedKeys[0].AccountHash.ToString()); } + + [Test] + public void GetNodeStatusResultTest() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/info_get_status-result.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual(ReactorState.Initialize, result.ReactorState); + Assert.AreEqual("1970-01-01T00:00:00.000Z", result.LastProgress); + Assert.AreEqual(4, result.AvailableBlockRange.Low); + Assert.AreEqual(5, result.AvailableBlockRange.High); + Assert.AreEqual("16ddf28e2b3d2e17f4cef36f8b58827eca917af225d139b0c77df3b4a67dc55e", result.BlockSync.Historical.BlockHash); + Assert.AreEqual(40, result.BlockSync.Historical.BlockHeight); + Assert.AreEqual("59907b1e32a9158169c4d89d9ce5ac9164fc31240bfcfb0969227ece06d74983", result.BlockSync.Forward.BlockHash); + Assert.IsNull(result.BlockSync.Forward.BlockHeight); + Assert.AreEqual("have block body(6701) for: block hash 5990..4983", result.BlockSync.Forward.AcquisitionState); + } } -} \ No newline at end of file +} diff --git a/Casper.Network.SDK.Test/TestData/info_get_status-result.json b/Casper.Network.SDK.Test/TestData/info_get_status-result.json new file mode 100644 index 0000000..5bd8a63 --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/info_get_status-result.json @@ -0,0 +1,45 @@ +{ + "peers": [ + { + "node_id": "tls:0101..0101", + "address": "127.0.0.1:54321" + } + ], + "api_version": "1.5.1", + "build_version": "1.5.1-6873c86", + "chainspec_name": "casper-example", + "starting_state_root_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_added_block_info": { + "hash": "13c2d7a68ecdd4b74bf4393c88915c836c863fc4bf11d7f2bd930a1bbccacdcb", + "timestamp": "2020-11-17T00:39:24.072Z", + "era_id": 1, + "height": 10, + "state_root_hash": "0808080808080808080808080808080808080808080808080808080808080808", + "creator": "01d9bf2148748a85c89da5aad8ee0b0fc2d105fd39d41a4c796536354f0ae2900c" + }, + "our_public_signing_key": "01d9bf2148748a85c89da5aad8ee0b0fc2d105fd39d41a4c796536354f0ae2900c", + "round_length": "1m 5s 536ms", + "next_upgrade": { + "activation_point": 42, + "protocol_version": "2.0.1" + }, + "uptime": "13s", + "reactor_state": "Initialize", + "last_progress": "1970-01-01T00:00:00.000Z", + "available_block_range": { + "low": 4, + "high": 5 + }, + "block_sync": { + "historical": { + "block_hash": "16ddf28e2b3d2e17f4cef36f8b58827eca917af225d139b0c77df3b4a67dc55e", + "block_height": 40, + "acquisition_state": "have strict finality(40) for: block hash 16dd..c55e" + }, + "forward": { + "block_hash": "59907b1e32a9158169c4d89d9ce5ac9164fc31240bfcfb0969227ece06d74983", + "block_height": null, + "acquisition_state": "have block body(6701) for: block hash 5990..4983" + } + } +} diff --git a/Casper.Network.SDK/Casper.Network.SDK.csproj b/Casper.Network.SDK/Casper.Network.SDK.csproj index c578cc0..ce4c526 100644 --- a/Casper.Network.SDK/Casper.Network.SDK.csproj +++ b/Casper.Network.SDK/Casper.Network.SDK.csproj @@ -1,10 +1,10 @@ - net5.0 - 1.1.2.0 - 1.1.2 - 1.1.2 + net7.0 + 2.0.0.0 + 2.0.0 + 2.0.0 Casper.Network.SDK make-software https://github.com/make-software/casper-net-sdk diff --git a/Casper.Network.SDK/JsonRpc/CasperMethods.cs b/Casper.Network.SDK/JsonRpc/CasperMethods.cs index 228b177..5027d0d 100644 --- a/Casper.Network.SDK/JsonRpc/CasperMethods.cs +++ b/Casper.Network.SDK/JsonRpc/CasperMethods.cs @@ -84,7 +84,7 @@ public GetAccountInfo(string publicKey, int height) : base("state_get_account_in this.Parameters.Add("public_key", publicKey); } } - + public class GetItem : RpcMethod { /// @@ -111,21 +111,14 @@ public class QueryGlobalState : RpcMethod /// A query to the global state that returns a stored value from the network. /// /// A global state key formatted as a string to query the value from the network. - /// A block hash or a state root hash. - /// true if hash is a Block hash. False for state root hash. + /// A block hash, a block height or a state root hash value. /// The path components starting from the key as base (use '/' as separator). - public QueryGlobalState(string key, string hash, bool isBlockHash, string[] path = null) : - base("query_global_state") + public QueryGlobalState(string key, StateIdentifier stateIdentifier, string[] path = null) : base("query_global_state") { - Dictionary stateIdentifier = new Dictionary - { - {isBlockHash ? "BlockHash" : "StateRootHash", hash} - }; - this.Parameters = new Dictionary { - {"state_identifier", stateIdentifier}, - {"path", path ?? new string[] {}}, + {"state_identifier", stateIdentifier.GetParam()}, + {"path", path ?? new string[] { }}, {"key", key} }; } @@ -146,6 +139,45 @@ public GetBalance(string purseURef, string stateRootHash) : base("state_get_bala {"purse_uref", purseURef} }; } + + public GetBalance(URef uref, StateIdentifier stateIdentifier) : base("query_balance") + { + Dictionary mainPurse = new Dictionary + { + {"purse_uref", uref.ToString()} + }; + this.Parameters = new Dictionary + { + {"purse_identifier", mainPurse} + }; + this.Parameters.Add("state_identifier", stateIdentifier.GetParam()); + } + + public GetBalance(AccountHashKey key, StateIdentifier stateIdentifier) : base("query_balance") + { + Dictionary mainPurse = new Dictionary + { + {"main_purse_under_account_hash", key.ToString()} + }; + this.Parameters = new Dictionary + { + {"purse_identifier", mainPurse} + }; + this.Parameters.Add("state_identifier", stateIdentifier.GetParam()); + } + + public GetBalance(PublicKey key, StateIdentifier stateIdentifier) : base("query_balance") + { + Dictionary mainPurse = new Dictionary + { + {"main_purse_under_public_key", key.ToString()} + }; + this.Parameters = new Dictionary + { + {"purse_identifier", mainPurse} + }; + this.Parameters.Add("state_identifier", stateIdentifier.GetParam()); + } } public class PutDeploy : RpcMethod @@ -178,8 +210,8 @@ public GetDeploy(string deployHash, bool finalizedApprovals = false) : base("inf { {"deploy_hash", deployHash} }; - - if(finalizedApprovals) + + if (finalizedApprovals) this.Parameters.Add("finalized_approvals", true); } } @@ -271,15 +303,17 @@ public GetDictionaryItem(string dictionaryItem, string stateRootHash) : base("st { this.Parameters = new Dictionary { - {"dictionary_identifier", new Dictionary { - {"Dictionary", dictionaryItem} - }}, + "dictionary_identifier", new Dictionary + { + {"Dictionary", dictionaryItem} + } + }, {"state_root_hash", stateRootHash}, }; } } - + public class GetDictionaryItemByAccount : RpcMethod { /// @@ -321,7 +355,8 @@ public class GetDictionaryItemByContract : RpcMethod /// The named key under which the dictionary seed URef is stored. /// The dictionary item key. /// Hash of the state root. - public GetDictionaryItemByContract(string contractKey, string dictionaryName, string dictionaryItem, string stateRootHash) : base("state_get_dictionary_item") + public GetDictionaryItemByContract(string contractKey, string dictionaryName, string dictionaryItem, + string stateRootHash) : base("state_get_dictionary_item") { var contractNamedKey = new Dictionary { @@ -329,13 +364,15 @@ public GetDictionaryItemByContract(string contractKey, string dictionaryName, st {"dictionary_name", dictionaryName}, {"dictionary_item_key", dictionaryItem} }; - + this.Parameters = new Dictionary { - {"dictionary_identifier", new Dictionary { - {"ContractNamedKey", contractNamedKey} - }}, + "dictionary_identifier", new Dictionary + { + {"ContractNamedKey", contractNamedKey} + } + }, {"state_root_hash", stateRootHash}, }; } @@ -349,20 +386,23 @@ public class GetDictionaryItemByURef : RpcMethod /// The dictionary's seed URef. /// The dictionary item key. /// Hash of the state root. - public GetDictionaryItemByURef(string seedURef, string dictionaryItem, string stateRootHash) : base("state_get_dictionary_item") + public GetDictionaryItemByURef(string seedURef, string dictionaryItem, string stateRootHash) : base( + "state_get_dictionary_item") { var contractNamedKey = new Dictionary { {"seed_uref", seedURef}, {"dictionary_item_key", dictionaryItem} }; - + this.Parameters = new Dictionary { - {"dictionary_identifier", new Dictionary { - {"URef", contractNamedKey} - }}, + "dictionary_identifier", new Dictionary + { + {"URef", contractNamedKey} + } + }, {"state_root_hash", stateRootHash}, }; } @@ -387,4 +427,38 @@ public GetRpcSchema() : base("rpc.discover") { } } + + public class GetChainspec : RpcMethod + { + /// + /// Returns the chainspec.toml file of the node. + /// + public GetChainspec() : base("info_get_chainspec") + { + } + } + + public class SpeculativeExecution : RpcMethod + { + /// + /// Sends a "deploy dry run" to the network. It will execute the deploy on top of the specified block and return + /// the results of the execution to the caller. The effects of the execution won't be committed to the trie + /// (blockchain database/GlobalState). + /// Endpoint can be used for debugging, discovery - for example price estimation. + /// + public SpeculativeExecution(Deploy deploy, string hash, bool isBlockHash) : base("speculative_exec") + { + this.Parameters = new Dictionary + { + {"deploy", deploy} + }; + if (hash != null) + { + this.Parameters.Add("state_identifier", new Dictionary + { + {isBlockHash ? "BlockHash" : "StateRootHash", hash} + }); + } + } + } } diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/GetBalanceResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/GetBalanceResult.cs index 140ae4c..db10152 100644 --- a/Casper.Network.SDK/JsonRpc/ResultTypes/GetBalanceResult.cs +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/GetBalanceResult.cs @@ -1,12 +1,16 @@ +using System; using System.Numerics; +using System.Text.Json; using System.Text.Json.Serialization; using Casper.Network.SDK.Converters; +using Casper.Network.SDK.Utils; namespace Casper.Network.SDK.JsonRpc.ResultTypes { /// /// Result for "state_get_balance" RPC response. /// + [JsonConverter(typeof(BalanceResultConverter))] public class GetBalanceResult : RpcResult { /// @@ -22,4 +26,60 @@ public class GetBalanceResult : RpcResult [JsonPropertyName("merkle_proof")] public string MerkleProof { get; init; } } -} \ No newline at end of file + + public class BalanceResultConverter : JsonConverter + { + public override GetBalanceResult Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + string api_version = null; + BigInteger balance = default; + string merkle_proof = null; + + reader.Read(); + + while (reader.TokenType == JsonTokenType.PropertyName) + { + var property = reader.GetString(); + reader.Read(); + switch (property) + { + case "api_version": + api_version = reader.GetString(); + reader.Read(); + break; + case "balance": + case "balance_value": + balance = BigInteger.Parse(reader.GetString()); + reader.Read(); + break; + case "merkle_proof": + merkle_proof = reader.GetString(); + reader.Read(); + break; + default: + throw new JsonException($"Unexpected property '{property}' deserializing QueryBalanceResult"); + } + } + + reader.Read(); + + return new GetBalanceResult + { + ApiVersion = api_version, + BalanceValue = balance, + MerkleProof = merkle_proof, + }; + } + + public override void Write( + Utf8JsonWriter writer, + GetBalanceResult ttlInMillis, + JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + } +} diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/GetChainspecResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/GetChainspecResult.cs new file mode 100644 index 0000000..bb1bbe3 --- /dev/null +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/GetChainspecResult.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; +using Casper.Network.SDK.Types; + +namespace Casper.Network.SDK.JsonRpc.ResultTypes +{ + public class GetChainspecResult + { + /// + /// The account returned. + /// + [JsonPropertyName("chainspec_bytes")] + public ChainspecRawBytes ChainspecBytes { get; init; } + } +} diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/GetDeployResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/GetDeployResult.cs index 402cd2d..bf2fa2a 100644 --- a/Casper.Network.SDK/JsonRpc/ResultTypes/GetDeployResult.cs +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/GetDeployResult.cs @@ -22,5 +22,17 @@ public class GetDeployResult : RpcResult [JsonPropertyName("execution_results")] [JsonConverter(typeof(GenericListConverter))] public List ExecutionResults { get; init; } + + /// + /// The hash of this deploy's block. + /// + [JsonPropertyName("block_hash")] + public string BlockHash { get; init; } + + /// + /// The height of this deploy's block. + /// + [JsonPropertyName("block_height")] + public ulong BlockHeight { get; init; } } } diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/GetNodeStatusResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/GetNodeStatusResult.cs index 1bb1462..528b70f 100644 --- a/Casper.Network.SDK/JsonRpc/ResultTypes/GetNodeStatusResult.cs +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/GetNodeStatusResult.cs @@ -58,5 +58,27 @@ public class GetNodeStatusResult : RpcResult /// Time that passed since the node has started. /// [JsonPropertyName("uptime")] public string Uptime { get; init; } + + /// + /// The current state of node reactor. + /// + [JsonPropertyName("reactor_state")] + [JsonConverter(typeof(JsonStringEnumConverter))] + public ReactorState ReactorState { get; init; } + + /// + /// Timestamp of the last recorded progress in the reactor. + /// + [JsonPropertyName("last_progress")] public string LastProgress { get; init; } + + /// + /// The available block range in storage. + /// + [JsonPropertyName("available_block_range")] public AvailableBlockRange AvailableBlockRange { get; init; } + + /// + /// The status of the block synchronizer builders. + /// + [JsonPropertyName("block_sync")] public BlockSynchronizerStatus BlockSync { get; init; } } -} \ No newline at end of file +} diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/SpeculativeExecutionResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/SpeculativeExecutionResult.cs new file mode 100644 index 0000000..b0144ca --- /dev/null +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/SpeculativeExecutionResult.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; +using Casper.Network.SDK.Types; + +namespace Casper.Network.SDK.JsonRpc.ResultTypes +{ + /// + /// Result for "speculative_exec" RPC response. + /// + public class SpeculativeExecutionResult : RpcResult + { + /// + /// The block hash + /// + [JsonPropertyName("block_hash")] + public string BlockHash { get; init; } + + /// + /// The result of executing the Deploy. + /// + [JsonPropertyName("execution_result")] + [JsonConverter(typeof(ExecutionResult.ExecutionResultConverter))] + public ExecutionResult ExecutionResult { get; init; } + } +} diff --git a/Casper.Network.SDK/NetCasperClient.cs b/Casper.Network.SDK/NetCasperClient.cs index 15dffb3..650bdaf 100644 --- a/Casper.Network.SDK/NetCasperClient.cs +++ b/Casper.Network.SDK/NetCasperClient.cs @@ -170,6 +170,19 @@ public async Task> QueryState(string keyHash, List(method); } + /// + /// Request the stored value in a global state key. + /// + /// The global state key formatted as a string to query the value from the network. + /// Height of the block to check the stored value in. + /// The path components starting from the key as base (use '/' as separator). + public async Task> QueryGlobalState(string key, int height, + string path = null) + { + var method = new QueryGlobalState(key, StateIdentifier.WithBlockHeight(height), path?.Split(new char[] {'/'})); + return await SendRpcRequestAsync(method); + } + /// /// Request the stored value in a global state key. /// @@ -182,7 +195,7 @@ public async Task> QueryGlobalState(string k if (stateRootHash == null) stateRootHash = await GetStateRootHash(); - var method = new QueryGlobalState(key, stateRootHash, isBlockHash: false, path?.Split(new char[] {'/'})); + var method = new QueryGlobalState(key, StateIdentifier.WithStateRootHash(stateRootHash), path?.Split(new char[] {'/'})); return await SendRpcRequestAsync(method); } @@ -207,7 +220,7 @@ public async Task> QueryGlobalState(GlobalSt public async Task> QueryGlobalStateWithBlockHash(string key, string blockHash, string path = null) { - var method = new QueryGlobalState(key, blockHash, isBlockHash: true, path?.Split(new char[] {'/'})); + var method = new QueryGlobalState(key, StateIdentifier.WithBlockHash(blockHash), path?.Split(new char[] {'/'})); return await SendRpcRequestAsync(method); } @@ -227,7 +240,7 @@ public async Task> QueryGlobalStateWithBlock /// Request a purse's balance from the network. /// /// Purse URef formatted as a string. - /// Hash of the state root. + /// Hash of the state root. Null to get latest available. public async Task> GetAccountBalance(string purseURef, string stateRootHash = null) { @@ -238,10 +251,9 @@ public async Task> GetAccountBalance(string purseU .GetProperty("main_purse").GetString(); } - if (stateRootHash == null) - stateRootHash = await GetStateRootHash(); + var uref = new URef(purseURef); - var method = new GetBalance(purseURef, stateRootHash); + var method = new GetBalance(uref, StateIdentifier.WithStateRootHash(stateRootHash)); return await SendRpcRequestAsync(method); } @@ -249,27 +261,110 @@ public async Task> GetAccountBalance(string purseU /// Request a purse's balance from the network. /// /// Purse URef key. - /// Hash of the state root. + /// Hash of the state root. Null to get latest available. public async Task> GetAccountBalance(URef purseURef, string stateRootHash = null) { - return await GetAccountBalance(purseURef.ToString(), stateRootHash); + var method = new GetBalance(purseURef, StateIdentifier.WithStateRootHash(stateRootHash)); + return await SendRpcRequestAsync(method); + } + + /// + /// Request the balance information of an account given its account hash key. + /// + /// The account hash of the account to request the balance. + /// Hash of the state root. Null to get latest available. + public async Task> GetAccountBalance(AccountHashKey accountHash, + string stateRootHash = null) + { + var method = new GetBalance(accountHash, StateIdentifier.WithStateRootHash(stateRootHash)); + return await SendRpcRequestAsync(method); } /// /// Request the balance information of an account given its public key. /// /// The public key of the account to request the balance. - /// Hash of the state root. - public async Task> GetAccountBalance(PublicKey publicKey, + /// Hash of the state root. Null to get latest available. + public async Task> GetAccountBalance(PublicKey publicKey, string stateRootHash = null) { - var response = await GetAccountInfo(publicKey); - var purseUref = response.Result.GetProperty("account") - .GetProperty("main_purse").GetString(); - return await GetAccountBalance(purseUref, stateRootHash); + var method = new GetBalance(publicKey, StateIdentifier.WithStateRootHash(stateRootHash)); + return await SendRpcRequestAsync(method); + } + + /// + /// Request a purse's balance from the network. + /// + /// Purse URef key. + /// Hash of the block. Null to get latest available. + public async Task> GetAccountBalanceWithBlockHash(URef purseURef, + string blockHash = null) + { + var method = new GetBalance(purseURef, StateIdentifier.WithBlockHash(blockHash)); + return await SendRpcRequestAsync(method); + } + + /// + /// Request the balance information of an account given its account hash key. + /// + /// The account hash of the account to request the balance. + /// Hash of the block. Null to get latest available. + public async Task> GetAccountBalanceWithBlockHash(AccountHashKey accountHash, + string blockHash = null) + { + var method = new GetBalance(accountHash, StateIdentifier.WithBlockHash(blockHash)); + return await SendRpcRequestAsync(method); } + /// + /// Request the balance information of an account given its public key. + /// + /// The public key of the account to request the balance. + /// Hash of the block. Null to get latest available. + public async Task> GetAccountBalanceWithBlockHash(PublicKey publicKey, + string blockHash = null) + { + var method = new GetBalance(publicKey, StateIdentifier.WithBlockHash(blockHash)); + return await SendRpcRequestAsync(method); + } + + /// + /// Request a purse's balance from the network. + /// + /// Purse URef key. + /// Height of the block. + public async Task> GetAccountBalance(URef purseURef, + int blockHeight) + { + var method = new GetBalance(purseURef, StateIdentifier.WithBlockHeight(blockHeight)); + return await SendRpcRequestAsync(method); + } + + /// + /// Request the balance information of an account given its account hash key. + /// + /// The account hash of the account to request the balance. + /// Height of the block. + public async Task> GetAccountBalance(AccountHashKey accountHash, + int blockHeight) + { + var method = new GetBalance(accountHash, StateIdentifier.WithBlockHeight(blockHeight)); + return await SendRpcRequestAsync(method); + } + + /// + /// Request the balance information of an account given its public key. + /// + /// The public key of the account to request the balance. + /// Height of the block. + public async Task> GetAccountBalance(PublicKey publicKey, + int blockHeight) + { + var method = new GetBalance(publicKey, StateIdentifier.WithBlockHeight(blockHeight)); + return await SendRpcRequestAsync(method); + } + /// /// Send a Deploy to the network for its execution. /// @@ -466,7 +561,6 @@ public async Task> GetDictionaryItemByContr /// The dictionary's seed URef. /// The dictionary item key. /// Hash of the state root. - /// public async Task> GetDictionaryItemByURef(string seedURef, string dictionaryItem, string stateRootHash = null) { @@ -496,6 +590,51 @@ public async Task GetRpcSchema() return response.Result.GetRawText(); } + /// + /// Request the the chainspec.toml, genesis accounts.toml, and global_state.toml files of the node. + /// + public async Task> GetChainspec() + { + var method = new GetChainspec(); + return await SendRpcRequestAsync(method); + } + + /// + /// Sends a "deploy dry run" to the network. It will execute the deploy on top of the specified block and return + /// the results of the execution to the caller. The effects of the execution won't be committed to the trie + /// (blockchain database/GlobalState). + /// This method runs in a different port of the network (e.g.: 7778) and can be used for debugging, discovery. + /// For example price estimation. + /// + /// The deploy to execute. + /// Hash of the state root. null if deploy is to be executed on top of the latest block. + public async Task> SpeceulativeExecution(Deploy deploy, string stateRootHash = null) + { + if (deploy.Approvals.Count == 0) + throw new Exception("Sign the deploy before sending it to the network."); + + var method = new SpeculativeExecution(deploy, stateRootHash, isBlockHash: false ); + return await SendRpcRequestAsync(method); + } + + /// + /// Sends a "deploy dry run" to the network. It will execute the deploy on top of the specified block and return + /// the results of the execution to the caller. The effects of the execution won't be committed to the trie + /// (blockchain database/GlobalState). + /// This method runs in a different port of the network (e.g.: 7778) and can be used for debugging, discovery. + /// For example price estimation. + /// + /// The deploy to execute. + /// Hash of the block on top of which the deploy is executed. + public async Task> SpeceulativeExecutionWithBlockHash(Deploy deploy, string blockHash = null) + { + if (deploy.Approvals.Count == 0) + throw new Exception("Sign the deploy before sending it to the network."); + + var method = new SpeculativeExecution(deploy, blockHash, isBlockHash: true ); + return await SendRpcRequestAsync(method); + } + /// /// Request the performance metrics of a node in the network. /// diff --git a/Casper.Network.SDK/Types/AvailableBlockRange.cs b/Casper.Network.SDK/Types/AvailableBlockRange.cs new file mode 100644 index 0000000..d2155f3 --- /dev/null +++ b/Casper.Network.SDK/Types/AvailableBlockRange.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace Casper.Network.SDK.Types +{ + /// + /// An unbroken, inclusive range of blocks. + /// + public class AvailableBlockRange + { + /// + /// The inclusive upper bound of the range. + /// + [JsonPropertyName("high")] + public ulong High { get; init; } + + /// + /// The inclusive lower bound of the range. + /// + [JsonPropertyName("low")] + public ulong Low { get; init; } + } +} diff --git a/Casper.Network.SDK/Types/BlockSynchronizerStatus.cs b/Casper.Network.SDK/Types/BlockSynchronizerStatus.cs new file mode 100644 index 0000000..2c93275 --- /dev/null +++ b/Casper.Network.SDK/Types/BlockSynchronizerStatus.cs @@ -0,0 +1,46 @@ +using System.Text.Json.Serialization; + +namespace Casper.Network.SDK.Types +{ + /// + /// The status of the block synchronizer. + /// + public class BlockSynchronizerStatus + { + /// + /// The status of syncing a historical block, if any. + /// + [JsonPropertyName("historical")] + public BlockSyncStatus Historical { get; init; } + + /// + /// The status of syncing a forward block, if any. + /// + [JsonPropertyName("forward")] + public BlockSyncStatus Forward { get; init; } + } + + /// + /// The status of syncing an individual block. + /// + public class BlockSyncStatus + { + /// + /// The block hash. + /// + [JsonPropertyName("block_hash")] + public string BlockHash { get; init; } + + /// + /// The block hash. + /// + [JsonPropertyName("block_height")] + public ulong? BlockHeight { get; init; } + + /// + /// The state of acquisition of the data associated with the block. + /// + [JsonPropertyName("acquisition_state")] + public string AcquisitionState { get; init; } + } +} diff --git a/Casper.Network.SDK/Types/ChainspecRawBytes.cs b/Casper.Network.SDK/Types/ChainspecRawBytes.cs new file mode 100644 index 0000000..e6965c7 --- /dev/null +++ b/Casper.Network.SDK/Types/ChainspecRawBytes.cs @@ -0,0 +1,53 @@ +using System.IO; +using System.Text; +using System.Text.Json.Serialization; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Casper.Network.SDK.Types +{ + /// + /// The raw bytes of the chainspec.toml, genesis accounts.toml, and global_state.toml files. + /// + public class ChainspecRawBytes + { + /// + /// Hex-encoded raw bytes of the current chainspec.toml file. + /// + [JsonPropertyName("chainspec_bytes")] + public string ChainspecBytes { get; init; } + + /// + /// Hex-encoded raw bytes of the current genesis accounts.toml file. + /// + [JsonPropertyName("maybe_genesis_accounts_bytes")] + public string MaybeGenesisAccountsBytes { get; init; } + + /// + /// Hex-encoded raw bytes of the current global_state.toml file. + /// + [JsonPropertyName("maybe_global_state_bytes")] + public string MaybeGlobalStateBytes { get; init; } + + /// + /// The current chainspec.toml file of the node. + /// + [JsonIgnore] + public string ChainspecAsString => Encoding.Default.GetString(Hex.Decode(ChainspecBytes)); + + /// + /// The current genesis accounts.toml file. + /// + [JsonIgnore] + public string GenesisAccountsAsString => MaybeGenesisAccountsBytes != null + ? Encoding.Default.GetString(Hex.Decode(MaybeGenesisAccountsBytes)) + : null; + + /// + /// The current global_state.toml file. + /// + [JsonIgnore] + public string GlobalStateAsString => MaybeGlobalStateBytes != null + ? Encoding.Default.GetString(Hex.Decode(MaybeGlobalStateBytes)) + : null; + } +} diff --git a/Casper.Network.SDK/Types/ContractPackage.cs b/Casper.Network.SDK/Types/ContractPackage.cs index efb62fc..1318dcb 100644 --- a/Casper.Network.SDK/Types/ContractPackage.cs +++ b/Casper.Network.SDK/Types/ContractPackage.cs @@ -58,6 +58,15 @@ public class Group [JsonConverter(typeof(GenericListConverter))] public List Keys { get; init; } } + + /// + /// A enum to determine the lock status of the contract package. + /// + public enum LockStatus + { + Locked, + Unlocked, + } /// /// Contract definition, metadata, and security container. @@ -87,5 +96,12 @@ public class ContractPackage /// [JsonPropertyName("versions")] public List Versions { get; init; } + + /// + /// The current state of node reactor. + /// + [JsonPropertyName("lock_status")] + [JsonConverter(typeof(JsonStringEnumConverter))] + public LockStatus LockStatus { get; init; } } -} \ No newline at end of file +} diff --git a/Casper.Network.SDK/Types/GlobalStateKey.cs b/Casper.Network.SDK/Types/GlobalStateKey.cs index 50757e8..6c036c5 100644 --- a/Casper.Network.SDK/Types/GlobalStateKey.cs +++ b/Casper.Network.SDK/Types/GlobalStateKey.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; @@ -10,7 +11,6 @@ namespace Casper.Network.SDK.Types { /// /// Keys in the global state store information about different data types. - /// /// public enum KeyIdentifier { @@ -55,9 +55,25 @@ public enum KeyIdentifier /// Dictionary = 0x09, /// + /// A `Key` variant under which system contract hashes are stored. + /// + SystemContractRegistry = 0x0a, + /// /// Era Summary keys store current era info. /// - EraSummary = 0x11, + EraSummary = 0x0b, + /// + /// A `Key` under which we store unbond information. + /// + Unbond = 0x0c, + /// + /// A `Key` variant under which chainspec and other hashes are stored. + /// + ChainspecRegistry = 0x0d, + /// + /// A `Key` variant under which a registry of checksums are stored. + /// + ChecksumRegistry = 0x0e, } /// @@ -111,6 +127,8 @@ public static GlobalStateKey FromString(string value) return new HashKey(value); if (value.StartsWith("contract-package-wasm")) return new HashKey(value.Replace("contract-package-wasm", "hash-")); + if (value.StartsWith("contract-package-")) + return new HashKey(value.Replace("contract-package-", "hash-")); if (value.StartsWith("contract-wasm-")) return new HashKey(value.Replace("contract-wasm-", "hash-")); if (value.StartsWith("contract-")) @@ -121,8 +139,6 @@ public static GlobalStateKey FromString(string value) return new TransferKey(value); if (value.StartsWith("deploy-")) return new DeployInfoKey(value); - if (value.StartsWith("era-")) - return new EraInfoKey(value); if (value.StartsWith("balance-")) return new BalanceKey(value); if (value.StartsWith("bid")) @@ -131,9 +147,20 @@ public static GlobalStateKey FromString(string value) return new WithdrawKey(value); if (value.StartsWith("dictionary")) return new DictionaryKey(value); + if (value.StartsWith("system-contract-registry-")) + return new SystemContractRegistryKey(value); if (value.StartsWith("era-summary-")) return new EraSummaryKey(value); - throw new ArgumentException($"Key not valid. Unknown key prefix."); + if (value.StartsWith("era-")) + return new EraInfoKey(value); + if (value.StartsWith("unbond-")) + return new UnbondKey(value); + if (value.StartsWith("chainspec-registry-")) + return new ChainspecRegistryKey(value); + if (value.StartsWith("checksum-registry-")) + return new ChecksumRegistryKey(value); + + throw new ArgumentException($"Key not valid. Unknown key prefix in \"{value}\"."); } /// @@ -154,6 +181,11 @@ public static GlobalStateKey FromBytes(byte[] bytes) 0x07 => new BidKey("bid-" + CEP57Checksum.Encode(bytes[1..])), 0x08 => new WithdrawKey("withdraw-" + CEP57Checksum.Encode(bytes[1..])), 0x09 => new DictionaryKey("dictionary-" + CEP57Checksum.Encode(bytes[1..])), + 0x0a => new SystemContractRegistryKey("system-contract-registry-" + CEP57Checksum.Encode(bytes[1..])), + 0x0b => new EraSummaryKey("era-summary-" + CEP57Checksum.Encode(bytes[1..])), + 0x0c => new UnbondKey("unbond-" + CEP57Checksum.Encode(bytes[1..])), + 0x0d => new ChainspecRegistryKey("chainspec-registry-" + CEP57Checksum.Encode(bytes[1..])), + 0x0e => new ChecksumRegistryKey("checksum-registry-" + CEP57Checksum.Encode(bytes[1..])), _ => throw new ArgumentException($"Unknown key identifier '{bytes[0]}'") }; } @@ -193,7 +225,12 @@ public override bool CanConvert(Type typeToConvert) typeToConvert == typeof(BalanceKey) || typeToConvert == typeof(BidKey) || typeToConvert == typeof(WithdrawKey) || - typeToConvert == typeof(DictionaryKey); + typeToConvert == typeof(DictionaryKey) || + typeToConvert == typeof(SystemContractRegistryKey) || + typeToConvert == typeof(EraSummaryKey) || + typeToConvert == typeof(UnbondKey) || + typeToConvert == typeof(ChainspecRegistryKey) || + typeToConvert == typeof(ChecksumRegistryKey); } public override JsonConverter CreateConverter( @@ -246,13 +283,15 @@ public override void Write( /// public class AccountHashKey : GlobalStateKey { - public AccountHashKey(string key) : base(key, "account-hash-") + public static string KEYPREFIX = "account-hash-"; + + public AccountHashKey(string key) : base(key, KEYPREFIX) { KeyIdentifier = KeyIdentifier.Account; } public AccountHashKey(PublicKey publicKey) - : base(publicKey.GetAccountHash(), "account-hash-") + : base(publicKey.GetAccountHash(), KEYPREFIX) { } } @@ -263,12 +302,14 @@ public AccountHashKey(PublicKey publicKey) /// public class HashKey : GlobalStateKey { - public HashKey(string key) : base(key, "hash-") + public static string KEYPREFIX = "hash-"; + + public HashKey(string key) : base(key, KEYPREFIX) { KeyIdentifier = KeyIdentifier.Hash; } - public HashKey(byte[] key) : this("hash-" + CEP57Checksum.Encode(key)) + public HashKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) { } } @@ -279,12 +320,14 @@ public HashKey(byte[] key) : this("hash-" + CEP57Checksum.Encode(key)) /// public class TransferKey : GlobalStateKey { - public TransferKey(string key) : base(key, "transfer-") + public static string KEYPREFIX = "transfer-"; + + public TransferKey(string key) : base(key, KEYPREFIX) { KeyIdentifier = KeyIdentifier.Transfer; } - public TransferKey(byte[] key) : this("transfer-" + CEP57Checksum.Encode(key)) + public TransferKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) { } } @@ -295,38 +338,27 @@ public TransferKey(byte[] key) : this("transfer-" + CEP57Checksum.Encode(key)) /// public class DeployInfoKey : GlobalStateKey { - public DeployInfoKey(string key) : base(key, "deploy-") + public static string KEYPREFIX = "deploy-"; + + public DeployInfoKey(string key) : base(key, KEYPREFIX) { KeyIdentifier = KeyIdentifier.DeployInfo; } - public DeployInfoKey(byte[] key) : this("deploy-" + CEP57Checksum.Encode(key)) + public DeployInfoKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) { } } - /// - /// Stores information related to the Auction metadata for a particular era.. - /// Format: u64 number with prefix 'era-' (e.g. 'era-3407'). - /// - public class EraInfoKey : GlobalStateKey + public abstract class U64GlobalStateKey : GlobalStateKey { - public EraInfoKey(string key) : base(key) + protected U64GlobalStateKey(string key) : base(key) { - KeyIdentifier = KeyIdentifier.EraInfo; - - if (!key.StartsWith("era-")) - throw new ArgumentException($"Key not valid. It should start with 'era-'.", - nameof(key)); - - if(!long.TryParse(key.Substring(4), out var eraNum)) - throw new ArgumentException($"Key not valid. Cannot parse era number.", - nameof(key)); } - + protected override byte[] _GetRawBytesFromKey(string key) { - var u64 = ulong.Parse(key.Substring(4)); + var u64 = ulong.Parse(key.Split('-').Last()); byte[] bytes = BitConverter.GetBytes(u64); if (!BitConverter.IsLittleEndian) Array.Reverse(bytes); @@ -342,6 +374,28 @@ public override byte[] GetBytes() return ms.ToArray(); } } + + /// + /// Stores information related to the Auction metadata for a particular era.. + /// Format: u64 number with prefix 'era-' (e.g. 'era-3407'). + /// + public class EraInfoKey : U64GlobalStateKey + { + public static string KEYPREFIX = "era-"; + + public EraInfoKey(string key) : base(key) + { + KeyIdentifier = KeyIdentifier.EraInfo; + + if (!key.StartsWith(KEYPREFIX)) + throw new ArgumentException($"Key not valid. It should start with '{KEYPREFIX}'.", + nameof(key)); + + if(!long.TryParse(key.Substring(KEYPREFIX.Length), out var eraNum)) + throw new ArgumentException($"Key not valid. Cannot parse era number.", + nameof(key)); + } + } /// /// Stores information related to the balance of a given purse. @@ -349,12 +403,14 @@ public override byte[] GetBytes() /// public class BalanceKey : GlobalStateKey { - public BalanceKey(string key) : base(key, "balance-") + public static string KEYPREFIX = "balance-"; + + public BalanceKey(string key) : base(key, KEYPREFIX) { KeyIdentifier = KeyIdentifier.Balance; } - public BalanceKey(byte[] key) : this("balance-" + CEP57Checksum.Encode(key)) + public BalanceKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) { } } @@ -365,12 +421,14 @@ public BalanceKey(byte[] key) : this("balance-" + CEP57Checksum.Encode(key)) /// public class BidKey : GlobalStateKey { - public BidKey(string key) : base(key, "bid-") + public static string KEYPREFIX = "bid-"; + + public BidKey(string key) : base(key, KEYPREFIX) { KeyIdentifier = KeyIdentifier.Bid; } - public BidKey(byte[] key) : this("bid-" + CEP57Checksum.Encode(key)) + public BidKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) { } } @@ -381,12 +439,14 @@ public BidKey(byte[] key) : this("bid-" + CEP57Checksum.Encode(key)) /// public class WithdrawKey : GlobalStateKey { - public WithdrawKey(string key) : base(key, "withdraw-") + public static string KEYPREFIX = "withdraw-"; + + public WithdrawKey(string key) : base(key, KEYPREFIX) { KeyIdentifier = KeyIdentifier.Withdraw; } - public WithdrawKey(byte[] key) : this("withdraw-" + CEP57Checksum.Encode(key)) + public WithdrawKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) { } } @@ -397,12 +457,38 @@ public WithdrawKey(byte[] key) : this("withdraw-" + CEP57Checksum.Encode(key)) /// public class DictionaryKey : GlobalStateKey { - public DictionaryKey(string key) : base(key, "dictionary-") + public static string KEYPREFIX = "dictionary-"; + + public DictionaryKey(string key) : base(key, KEYPREFIX) { KeyIdentifier = KeyIdentifier.Dictionary; } - public DictionaryKey(byte[] key) : this("dictionary-" + CEP57Checksum.Encode(key)) + public DictionaryKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) + { + } + } + + /// + /// Stores a list of system contract hashes (auction, + /// handle payment, mint and standard payment). + /// Format: 32-byte length with prefix 'system-contract-registry-'. + /// + public class SystemContractRegistryKey : GlobalStateKey + { + public static string KEYPREFIX = "system-contract-registry-"; + + public SystemContractRegistryKey() : this( + KEYPREFIX + "0000000000000000000000000000000000000000000000000000000000000000") + { + } + + public SystemContractRegistryKey(string key) : base(key, KEYPREFIX) + { + KeyIdentifier = KeyIdentifier.SystemContractRegistry; + } + + public SystemContractRegistryKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) { } } @@ -413,9 +499,76 @@ public DictionaryKey(byte[] key) : this("dictionary-" + CEP57Checksum.Encode(key /// public class EraSummaryKey : GlobalStateKey { - public EraSummaryKey(string key) : base(key, "era-summary-") + public static string KEYPREFIX = "era-summary-"; + + public EraSummaryKey(string key) : base(key, KEYPREFIX) { KeyIdentifier = KeyIdentifier.EraSummary; } } + + /// + /// Stores unbond information, + /// Format: 32-byte length with prefix 'unbond-'. + /// + public class UnbondKey : GlobalStateKey + { + public static string KEYPREFIX = "unbond-"; + + public UnbondKey(string key) : base(key, KEYPREFIX) + { + KeyIdentifier = KeyIdentifier.Unbond; + } + + public UnbondKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) + { + } + } + + /// + /// Stores a mapping of file names to the hash of the file itself. These files include *Chainspec.toml* and may + /// also include *Accounts.toml* and *GlobalState.toml*. + /// Format: 32-byte length with prefix 'chainspec-registry-'. + /// + public class ChainspecRegistryKey : GlobalStateKey + { + public static string KEYPREFIX = "chainspec-registry-"; + + public ChainspecRegistryKey() : this( + KEYPREFIX + "0000000000000000000000000000000000000000000000000000000000000000") + { + } + + public ChainspecRegistryKey(string key) : base(key, KEYPREFIX) + { + KeyIdentifier = KeyIdentifier.ChainspecRegistry; + } + + public ChainspecRegistryKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) + { + } + } + + /// + /// Stores a registry of checksums. + /// Format: 32-byte length with prefix 'chainspec-registry-'. + /// + public class ChecksumRegistryKey : GlobalStateKey + { + public static string KEYPREFIX = "checksum-registry-"; + + public ChecksumRegistryKey() : this( + KEYPREFIX + "0000000000000000000000000000000000000000000000000000000000000000") + { + } + + public ChecksumRegistryKey(string key) : base(key, KEYPREFIX) + { + KeyIdentifier = KeyIdentifier.ChecksumRegistry; + } + + public ChecksumRegistryKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) + { + } + } } diff --git a/Casper.Network.SDK/Types/ReactorState.cs b/Casper.Network.SDK/Types/ReactorState.cs new file mode 100644 index 0000000..f8c0c00 --- /dev/null +++ b/Casper.Network.SDK/Types/ReactorState.cs @@ -0,0 +1,15 @@ +namespace Casper.Network.SDK.Types +{ + /// + /// The state of the reactor. + /// + public enum ReactorState + { + Initialize, + CatchUp, + Upgrading, + KeepUp, + Validate, + ShutdownForUpgrade + } +} diff --git a/Casper.Network.SDK/Types/StateIdentifier.cs b/Casper.Network.SDK/Types/StateIdentifier.cs new file mode 100644 index 0000000..c951a80 --- /dev/null +++ b/Casper.Network.SDK/Types/StateIdentifier.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; + +namespace Casper.Network.SDK.Types +{ + public class StateIdentifier + { + private string _stateRootHash; + private string _blockHash; + private int? _blockHeight; + + private StateIdentifier() + { + } + + public static StateIdentifier WithStateRootHash(string stateRootHash) + { + return new StateIdentifier + { + _stateRootHash = stateRootHash, + _blockHash = null, + _blockHeight = null + }; + } + + public static StateIdentifier WithBlockHash(string blockHash) + { + return new StateIdentifier + { + _stateRootHash = null, + _blockHash = blockHash, + _blockHeight = null + }; + } + + public static StateIdentifier WithBlockHeight(int blockHeight) + { + return new StateIdentifier + { + _stateRootHash = null, + _blockHash = null, + _blockHeight = blockHeight + }; + } + + public Dictionary GetParam() + { + if (_stateRootHash != null) + return new Dictionary { {"StateRootHash" , _stateRootHash } }; + if (_blockHash != null) + return new Dictionary { {"BlockHash" , _blockHash } }; + if (_blockHeight != null) + return new Dictionary { {"BlockHeight" , _blockHeight } }; + + return null; + } + } +} diff --git a/Casper.Network.SDK/Types/Transform.cs b/Casper.Network.SDK/Types/Transform.cs index 6b6bcdd..6b424ab 100644 --- a/Casper.Network.SDK/Types/Transform.cs +++ b/Casper.Network.SDK/Types/Transform.cs @@ -28,7 +28,8 @@ public enum TransformType AddUInt256, AddUInt512, AddKeys, - Failure + Failure, + WriteUnbonding, } /// @@ -151,6 +152,10 @@ public override Transform Read( value = reader.GetString(); reader.Read(); break; + case TransformType.WriteUnbonding: + value = JsonSerializer.Deserialize>(ref reader, options); + reader.Read(); + break; } reader.Read(); //end object @@ -180,4 +185,4 @@ public override void Write( } } } -} \ No newline at end of file +} diff --git a/Casper.Network.SDK/Types/URef.cs b/Casper.Network.SDK/Types/URef.cs index e5d2957..aff2b64 100644 --- a/Casper.Network.SDK/Types/URef.cs +++ b/Casper.Network.SDK/Types/URef.cs @@ -12,14 +12,16 @@ namespace Casper.Network.SDK.Types /// public class URef : GlobalStateKey { + public static string KEYPREFIX = "uref-"; + public AccessRights AccessRights { get; } public URef(string value) : base(value) { KeyIdentifier = KeyIdentifier.URef; - if (!value.StartsWith("uref-")) - throw new ArgumentException($"Key not valid. It should start with 'uref-'.", + if (!value.StartsWith(KEYPREFIX)) + throw new ArgumentException($"Key not valid. It should start with '{KEYPREFIX}'.", nameof(value)); var parts = value.Substring(5).Split(new char[] {'-'}); @@ -43,7 +45,7 @@ public URef(string value) : base(value) /// Creates an URef from a 33 bytes array. Last byte corresponds to the access rights. /// public URef(byte[] bytes) - : this($"uref-{Hex.ToHexString(bytes[..32])}-{(int)bytes[32]:000}") + : this($"{KEYPREFIX}{Hex.ToHexString(bytes[..32])}-{(int)bytes[32]:000}") { } @@ -51,7 +53,7 @@ public URef(byte[] bytes) /// Creates an URef from a 32 bytes array and the access rights. /// public URef(byte[] rawBytes, AccessRights accessRights) - : this($"uref-{Hex.ToHexString(rawBytes)}-{(int)accessRights:000}") + : this($"{KEYPREFIX}{Hex.ToHexString(rawBytes)}-{(int)accessRights:000}") { } @@ -73,7 +75,7 @@ public override byte[] GetBytes() public override string ToString() { - return "uref-" + CEP57Checksum.Encode(RawBytes) + $"-{(byte) AccessRights:000}"; + return KEYPREFIX + CEP57Checksum.Encode(RawBytes) + $"-{(byte) AccessRights:000}"; } } -} \ No newline at end of file +} diff --git a/Casper.Network.SDK/Types/UnbondingPurse.cs b/Casper.Network.SDK/Types/UnbondingPurse.cs index 2f6c921..dfe3557 100644 --- a/Casper.Network.SDK/Types/UnbondingPurse.cs +++ b/Casper.Network.SDK/Types/UnbondingPurse.cs @@ -39,5 +39,12 @@ public class UnbondingPurse [JsonPropertyName("validator_public_key")] [JsonConverter(typeof(PublicKey.PublicKeyConverter))] public PublicKey ValidatorPublicKey { get; init; } + + /// + /// The validator public key to re-delegate to. + /// + [JsonPropertyName("new_validator")] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey NewValidator { get; init; } } -} \ No newline at end of file +} diff --git a/LICENSE b/LICENSE index 425acb5..5a1456a 100644 --- a/LICENSE +++ b/LICENSE @@ -199,4 +199,3 @@ 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. -