diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fd24e08 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +FROM ruby:3.3.4-slim + +WORKDIR /usr/src/scale-rb + +COPY . . + +RUN apt-get update && \ + apt-get upgrade -y && \ + apt-get install curl iptables build-essential \ + git wget jq vim make gcc nano tmux htop nvme-cli \ + pkg-config libssl-dev libleveldb-dev libgmp3-dev \ + tar clang bsdmainutils ncdu unzip llvm libudev-dev \ + make protobuf-compiler -y && \ + # install and configure rustup and minimal components + curl -L "https://static.rust-lang.org/rustup/dist/x86_64-unknown-linux-gnu/rustup-init" \ + -o rustup-init; \ + chmod +x rustup-init; \ + ./rustup-init -y --no-modify-path --profile minimal --default-toolchain stable; \ + echo 'export PATH="/usr/local/cargo/bin:$PATH"' >> "${HOME}/.bashrc" && \ + \. "${HOME}/.bashrc" && \ + \. "$HOME/.cargo/env" && \ + rustup toolchain install nightly && \ + rustup default nightly && \ + rustup update && \ + rustup update nightly && \ + rustup target add wasm32-unknown-unknown --toolchain nightly && \ + rustup show && \ + cargo --version && \ + gem install bundler:2.1.4 && \ + bundle install && \ + # install gem locally + rake install:local + +CMD tail -f /dev/null \ No newline at end of file diff --git a/README.md b/README.md index d6f4f40..db8a1e2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,37 @@ # ScaleRb -It is still under heavy development. Use the latest version. +*WARNING: It is still under heavy development. Use the latest version.* + +## Demo + +### Docker + +* Install and run Docker + +* Build and run Docker container +```bash +docker build --platform linux/x86_64 -f ./Dockerfile --tag scale-rb:v0.2.2 ./ +docker run --platform linux/x86_64 \ + --network host \ + -it -d --hostname scale-rb --name scale-rb --volume ./:/scale-rb:rw scale-rb:v0.2.2 +``` +Enter shell of Docker container +```bash +docker exec -it scale-rb /bin/bash +``` + +* Build and run Ruby program in Docker container +```bash +bundle install +ruby example01/main.rb +``` + +* Build and run Ruby program in Docker container +```bash +bundle install +cd example02/ +rspec +``` ## Installation diff --git a/example01/main.rb b/example01/main.rb new file mode 100644 index 0000000..eee8b51 --- /dev/null +++ b/example01/main.rb @@ -0,0 +1,47 @@ +require "scale_rb" + +# WS connection + +ScaleRb.logger.level = Logger::DEBUG + +ScaleRb::WsClient.start('wss://polkadot-rpc.dwellir.com') do |client| + puts client + block_hash = client.chain_getBlockHash(21585684) + puts block_hash + runtime_version = client.state_getRuntimeVersion(block_hash) + puts runtime_version[:specName] + puts runtime_version[:specVersion] + + count = 0 + + subscription_id = client.chain_subscribeNewHead do |head| + count = count + 1 + + if count < 5 + block_number = head[:number].to_i(16) + block_hash = client.chain_getBlockHash(block_number) + puts "Received new head at height: #{block_number}, block hash: #{block_hash}" + else + unsub_result = client.chain_unsubscribeNewHead(subscription_id) + puts "Unsubscribed from new heads: #{unsub_result}" + end + end + + puts "Subscribed to new heads with subscription id: #{subscription_id}" + + block_number = 21711742 + block_hash = client.chain_getBlockHash(block_number) + metadata = client.get_metadata(block_hash) + + puts "event count: #{client.get_storage('System', 'EventCount', block_hash:, metadata:)}" + # event count: 48 + + puts "treasury proposal #854: #{client.get_storage('Treasury', 'Proposals', [854], block_hash:, metadata:)}" + # treasury proposal #854: {:proposer=>"0xb6f0f10eec993f3e6806eb6cc4d2f13d5f5a90a17b855a7bf9847a87e07ee322", :value=>82650000000000, :beneficiary=>"0xb6f0f10eec993f3e6806eb6cc4d2f13d5f5a90a17b855a7bf9847a87e07ee322", :bond=>0} + + puts "all treasury proposals: #{client.get_storage('Treasury', 'Proposals', block_hash:, metadata:)}" + # all treasury proposals: [{:storage_key=>"0x89d139e01a5eb2256f222e5fc5dbe6b388c2f7188c6fdd1dffae2fa0d171f4400c1910093df9204856030000", :storage=>{:proposer=>"0xb6f0f10eec993f3e6806eb6cc4d2f13d5f5a90a17b855a7bf9847a87e07ee322", :value=>82650000000000, :beneficiary=>"0xb6f0f10eec993f3e6806eb6cc4d2f13d5f5a90a17b855a7bf9847a87e07ee322", :bond=>0}}, ...] + + puts "child bounties: #{client.get_storage('ChildBounties', 'ChildBounties', [11, 1646], block_hash:, metadata:)}" + # child bounties: {:parent_bounty=>11, :value=>3791150000000, :fee=>0, :curator_deposit=>0, :status=>{:PendingPayout=>{:curator=>"0xb1725c0de514e0df808b19dbfca26672019ea5f9e2eb69c0055c7f1d01b4f18a", :beneficiary=>"0xb089dedc24a15308874dc862b035d74f2f7b45cad475d6121a2d944921bbe237", :unlock_at=>21703671}}} +end diff --git a/example02/.rspec b/example02/.rspec new file mode 100644 index 0000000..c99d2e7 --- /dev/null +++ b/example02/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/example02/Gemfile b/example02/Gemfile new file mode 100644 index 0000000..f46fd4e --- /dev/null +++ b/example02/Gemfile @@ -0,0 +1,6 @@ +source 'https://rubygems.org' + +group :test do + gem 'cucumber', '~> 9.2.0' + gem 'rspec', '~> 3.13.0' +end diff --git a/example02/Gemfile.lock b/example02/Gemfile.lock new file mode 100644 index 0000000..6064755 --- /dev/null +++ b/example02/Gemfile.lock @@ -0,0 +1,60 @@ +GEM + remote: https://rubygems.org/ + specs: + bigdecimal (3.1.8) + builder (3.2.4) + cucumber (9.2.0) + builder (~> 3.2) + cucumber-ci-environment (> 9, < 11) + cucumber-core (> 13, < 14) + cucumber-cucumber-expressions (~> 17.0) + cucumber-gherkin (> 24, < 28) + cucumber-html-formatter (> 20.3, < 22) + cucumber-messages (> 19, < 25) + diff-lcs (~> 1.5) + mini_mime (~> 1.1) + multi_test (~> 1.1) + sys-uname (~> 1.2) + cucumber-ci-environment (10.0.1) + cucumber-core (13.0.2) + cucumber-gherkin (>= 27, < 28) + cucumber-messages (>= 20, < 23) + cucumber-tag-expressions (> 5, < 7) + cucumber-cucumber-expressions (17.1.0) + bigdecimal + cucumber-gherkin (27.0.0) + cucumber-messages (>= 19.1.4, < 23) + cucumber-html-formatter (21.3.1) + cucumber-messages (> 19, < 25) + cucumber-messages (22.0.0) + cucumber-tag-expressions (6.1.0) + diff-lcs (1.5.1) + ffi (1.16.3) + mini_mime (1.1.5) + multi_test (1.1.0) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.0) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.1) + sys-uname (1.2.3) + ffi (~> 1.1) + +PLATFORMS + arm64-darwin-23 + ruby + +DEPENDENCIES + cucumber (~> 9.2.0) + rspec (~> 3.13.0) + +BUNDLED WITH + 2.5.11 diff --git a/example02/lib/example02.rb b/example02/lib/example02.rb new file mode 100644 index 0000000..7e5d781 --- /dev/null +++ b/example02/lib/example02.rb @@ -0,0 +1,71 @@ +# I own a DAO +# I need one or more cores to run it +# Each core costs x KSM per month +# Write software to manage the cores + +class User + def initialize(name, parachain_dao) + @name = name + @parachain_dao = parachain_dao + end + + attr_accessor :name, :parachain_dao +end + +class ParachainDAO + def initialize(name, relay_chain) + @name = name + @relay_chain = relay_chain + self.to_s + end + + attr_accessor :name, :relay_chain + + def to_s + "Created parachain DAO #{@name} on #{@relay_chain}" + end +end + +class Core + def initialize(id, parachain_dao) + @id = id + @parachain_dao = parachain_dao + @renewals_count = 0 + + puts "Created core with #{@id}" + end + + attr_accessor :id, :parachain_dao, :renewals_count + + def autorenew + # poll for when able to renew when interlude period starts + # renew when ready + if is_ready_to_renew? self.id then + self.renew + end + + # TODO - emit event notification when renewed until to watch + end + + def is_ready_to_renew?(core_id) + # TODO - RPC call to broker pallet + sleep 2 + true + end + + def renew + # poll for when markets change above threshold + if met_market_threshold? then + # TODO - check sufficient balance + # TODO - RPC call to broker pallet to renew + puts "Renewed core id #{@id} for parachain DAO #{@parachain_dao.name} on #{@parachain_dao.relay_chain}" + self.renewals_count += 1 + true + end + end + + def met_market_threshold? + # TODO - poll market conditions + true + end +end diff --git a/example02/spec/example02_spec.rb b/example02/spec/example02_spec.rb new file mode 100644 index 0000000..71c871f --- /dev/null +++ b/example02/spec/example02_spec.rb @@ -0,0 +1,18 @@ +require './lib/example02' + +describe Core do + context "When testing the Core class" do + + before do + @parachain_dao = ParachainDAO.new("BlockDevRels", :kusama_coretime) + puts @parachain_dao.to_s + @user = User.new("Sasha", @parachain_dao) + @core = Core.new(1, @parachain_dao) + @core.autorenew + end + + it "#autorenew" do + expect(@core.renewals_count).to be(1) + end + end +end \ No newline at end of file diff --git a/example02/spec/spec_helper.rb b/example02/spec/spec_helper.rb new file mode 100644 index 0000000..c80d44b --- /dev/null +++ b/example02/spec/spec_helper.rb @@ -0,0 +1,98 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end