Alexandre Baldé
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    4
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    {%hackmd @themes/orangeheart %} # Notes On [Polkadot Ecosystem Tests](https://github.com/open-web3-stack/polkadot-ecosystem-tests/tree/master) (PET) ## Background on PET These links are not required reading in order to get started with PET; they're helpful context on the origin, development and goals of the project. See: 1. https://github.com/polkadot-fellows/runtimes/issues/171 2. https://forum.polkadot.network/t/polkadot-ecosystem-tests/9067 3. [Ecosystem Test Environment Bounty](https://polkadot.polkassembly.io/referenda/804) TL;DR * Testing initiatives had been, for some time, underway at Parity and elsewhere (see [`polkadot-integration-tests` repo](https://github.com/paritytech/polkadot-integration-tests)) * PET was inspired by Acala network's [`e2e-tests` repo](https://github.com/AcalaNetwork/e2e-tests/), created to identify regressions in XCM behavior * It was created to satisfy an OpenGov bounty requesting creation of an ecosystem test environment (link no. 3) * Main purpose is to be a visible central hub of testing efforts, allowing contributions by parachain teams and ecosystem builders ## Before starting **HIGHLY RECOMMENDED**: 1. clone https://github.com/AcalaNetwork/chopsticks, and 2. skim the `README.md` for how to start a simple network At times, it helps to have a local relay chain with one/several parachain(s) in order to locally verify some behavior, or confirm/disprove a hypothesis from a test scenario. Having `chopsticks` ready to go will help save time. Take note of the repository's `./configs/*.yml` directory and files. This is where the created network's storage can be changed to match that of the test scenario, to reproduce the desired effect. ## Intro To get started with the repository, see [the project's `README.md`](https://github.com/open-web3-stack/polkadot-ecosystem-tests/blob/master/README.md) for guidance on how to install dependencies and run tests. There's also an overview of [the project's structure](https://github.com/open-web3-stack/polkadot-ecosystem-tests/blob/master/README.md), and how to add more tests, or [new kinds](https://github.com/open-web3-stack/polkadot-ecosystem-tests/blob/master/README.md) of tests. ## While developing ### `chopsticks` + PJS Chopsticks + polkadot.js is a useful combined work environment that should be used while preparing contributions to the PET. If * it's not clear how to structure the TypeScript for a test using `'@polkadot/types/` or `@polkadot-api`, or * to further develop an idea for a scenario, spin up a chopsticks network and mock the intended scenario with PJS. This can be done either 1. manually building, signing and sending calls with the "Extrinsics" tab in the "Developer" dropdown menu, or 2. via the "`</>`JavaScript" option in the same menu #### Inspecting running tests This is already explained in PET's `README.md`: to debug a test _while_ it's running, use `await client.pause()`; below are some practical considerations on doing so. Client pausing can be done on more than one chain - if the test scenario has both a relay chain and a parachain, one may: 1. insert `relayClient.pause()` - note the lack of `await` - with it, the thread executing the `.js` would idle indefinitely without moving on to the next `pause()` calls, if any exist 3. for every parachain, add a `paraClient.pause()` - if it's the **last** parachain that must be paused, use `await paraClient.pause()` 3. inspect `STDOUT` for port data on all chains - the shown endpoint will be `https://polkadot.js.org/apps/?rpc=ws://[::]:<port_number>`. It may be necessary to change `[::]` to `[::1]` (IPv6 loopback) when pasting the address to PJS. 5. connect to relevant chains via PJS - as many tabs as desired can be used for each - it is at times relevant to have "JavaScript" and "Chain State" open for each chain, to execute XCM calls and observe results origin-side and destination-side **IMPORTANT**: temporarily modify Vitest's global variable `testTimeout` in `polkadot-ecosystem-tests/vitest.config.mts` if experimenting on PJS for prolonged periods - its default is 240 seconds. ### Concrete notes on testing PET guide on most common testing pattern: [here](https://github.com/open-web3-stack/polkadot-ecosystem-tests?tab=readme-ov-file#key-testing-patterns). The previous section won't be reproduced here in full; important takeaways below. #### Block times Calling `await client.dev.newBlock()` can take anywhere from 1 - 10 seconds, depending on conditions: CPU and network roundtrips (for interactions that require RPC fetches). #### Snapshots Prefer `vitest` [snapshots](https://vitest.dev/guide/snapshot) over `assert`s on concrete objects or values - both can coexist, but when in doubt or if compelled, prefer snapshots. This is for a few reasons: 1. snapshots allow use of `chopsticks-testing` to easily [redact](https://github.com/AcalaNetwork/chopsticks?tab=readme-ov-file#testing-with-acala-networkchopsticks-testing) irrelevant information from chain data - time-sensitive information such as timestamps/block and era counts should be excised to not cause spurious test failures 2. simultaneously, snapshots allow the storage of detailed information on the execution of an extrinsic/storage query - more practical than an `assert` for every field in a large object. - In the event that e.g. the data or structure of an event or an extrinsic change, PET's CI will automatically raise a GH issue with the offending snapshot - this would be not easy to do with `assert`s. ##### Examples Below are two examples of how snapshots serve as an automated error detection mechanism. * [#61](https://github.com/open-web3-stack/polkadot-ecosystem-tests/pull/61), [#63](https://github.com/open-web3-stack/polkadot-ecosystem-tests/pull/63) introduced E2E tests to the People chains + This PR found that `pallet_identity::{rename_sub, set_subs}` were missing events, so E2E tests for those extrinsics purposefully [include](https://github.com/open-web3-stack/polkadot-ecosystem-tests/pull/63/files#diff-c903afdb98d10e076ecab21537d36dac30e0b90dd0b62cada9033d7f1e96ed21R435) [snapshots](https://github.com/open-web3-stack/polkadot-ecosystem-tests/pull/63/files#diff-c903afdb98d10e076ecab21537d36dac30e0b90dd0b62cada9033d7f1e96ed21R482) with the ([deliberately](https://github.com/open-web3-stack/polkadot-ecosystem-tests/blob/3e5730a0b0d7ff192731f9fc515a3d0c43a94641/packages/kusama/src/__snapshots__/people.kusama.e2e.test.ts.snap#L317) [empty](https://github.com/open-web3-stack/polkadot-ecosystem-tests/blob/3e5730a0b0d7ff192731f9fc515a3d0c43a94641/packages/kusama/src/__snapshots__/people.kusama.e2e.test.ts.snap#L331)) events emitted by the execution of those extrinsics. + The intent is that, when the upstream [`polkadot-sdk` PR](https://github.com/paritytech/polkadot-sdk/pull/6261) that fixes the uncovered problem reaches the production runtimes, it will trigger a sequence of failed automated CI runs that will raise an issue in the PET repo. * [#160](https://github.com/open-web3-stack/polkadot-ecosystem-tests/pull/160), [#115](https://github.com/open-web3-stack/polkadot-ecosystem-tests/pull/115) introduced E2E tests to governance features. As above: + a missing feature was detected (no events for `pallet_conviction_voting::{vote, remove_vote, remove_other_vote}`), + a [PR](https://github.com/paritytech/polkadot-sdk/pull/6544) was raised, and + [empty snapshots](https://github.com/rockbmb/polkadot-ecosystem-tests/blob/ab7735cb76eb512988e41331d8e028a0539b5f6d/packages/kusama/src/__snapshots__/kusama.governance.e2e.test.ts.snap#L165) were included to deliberately trigger a failure, so that + tests can then be fixed to include the new behavior (whose original error they uncovered) ##### Note The above method of 1. having empty snapshots to stand in for coming fixes 2. awaiting for said fixes to be downstreamed and correcting PET as needed also has a converse: 1. not-empty snapshots demonstrate the structure produced by the currently deployed runtimes 2. should a CI failure be caused by differing snapshots, this indicates (potentially unintended) changes introduced in `polkadot-sdk`. TL;DR always inspect the snapshot diffs in CI failures. ## Scheduling tasks It may be necessary to execute a call with a certain origin, or to manually schedule a call. In these cases, check the `schedule*CallWithOrigin` helpers in `packages/shared` - they directly modify the chopsticks node's storage to inject an entry into the agenda of the scheduler pallet. Recall that the scheduler's agenda is a key-value map keyyed by block numbers to lists of tasks scheduled for execution in that block: ```rust #[pallet::storage] pub type Agenda<T: Config> = StorageMap< BlockNumberFor<T>, BoundedVec<Option<ScheduledOf<T>>, T::MaxScheduledPerBlock>, >; ``` Internally, what this does is similar to the technique used in the guides linked in the last section: ```typescript const origin = ... // Get the target scheduling block number. // How this is done depends on the block provider - local, nonlocal. const number = ... const extrinsic = await client.api.tx.pallet.extrinsinc(...args) await api.rpc('dev_setStorage', { scheduler: { agenda: [ [ [number], [ { call: { Inline: extrinsic.method.toHex() }, origin, } ] ] ] } }) await api.rpc('dev_newBlock', { count: 1 }) ``` #### Scheduling with different origins See {%preview https://substrate.stackexchange.com/questions/12181/how-to-use-chopsticks-to-test-a-call-with-stakingadmin-origin %} for how to use origins other than `Root`. #### Events resulting from manually scheduled tasks If a task is executed as the result of a manual scheduling, then any events it emits - whether is succeeds or fails - will not be found with the otherwise functional method of ```typescript const events = await client.api.query.system.events() const specificEvent = events.find((record) => { const { event } = record return event.section === 'pallet' && event.method === 'EventName' }) ``` Only `scheduler.Dispatched` will be found in this case. To verify that the call did indeed succeed or fail in whichever way was expected, use `checkSystemEvents`, whose API is much like `checkEvent`'s: it allows filtering by pallet allone, or by section + method like in this example. #### AHM With the AHM, the scheduler pallet's agenda on Asset Hubs will be keyed by a non-local block provider - the relay chain's. This introduces a semantic change in the above technique: 1. when at block `r` of a chopsticks relay, the task is to be manually scheduled for `r + 1`. After a `dev.newBlock`, it will be executed 2. when at block `p` of a chopsticks parachain, with `n` being the result of accessing `parachainSystem.lastRelayBlockNumber` at block `p`, the task is to be scheduled for `n` -- **and NOT** `p + 1` or `n + 1` Note *also* that the Asset Hub system parachain's scheduler pallet may or may not have Async Backing (AB) enabled. AB being enabled means [`chopsticks` will increment](https://github.com/AcalaNetwork/chopsticks/blob/d753769715cab36146ca6790260f7424cdca4251/packages/core/src/blockchain/inherent/parachain/validation-data.ts#L143-L152) `parachainSystem.lastRelayBlockNumber` in steps of 2 blocks. --- Accounting for this requires giving tests a config to allow for runtime introspection, to calculate offsets depending on the environment they are running on (relay/para, AB enabled/disabled). The alternative is to have 2 sets of test suites, mostly copy-pasted, to run with live production runtimes, and during AHM development/testing. ## Regarding the traversal of long time spans Above, it was said that, in a `chopsticks` local network, block creation times are between 1 to 10 seconds. In some cases, one may wish to test states or actions that require the passing of a large number of blocks. Examples: 1. awaiting a referendum's timeout 2. observing an era change 3. fully unbonding bonded funds To test these, it is not feasible to wait for the whole period: ```typescript // Unbonding period on Polkadot is 28 days await client.dev.newBlock({ count: 28 * ((24 * 60 * 60) / 6) }) ``` RPC timeout errors are certain, and even altering the timeout can still lead to `Abnormal closure` of the RPC endpoint the `chopsticks` instance is connected to. ### Mitigations TODO ## Events ### Scrutinizing events To check the most recent block's events, consider the following example: ```typescript const events = await baseClient.api.query.system.events() const killedAccountEvent = events.find((record) => { const { event } = record return event.section === 'system' && event.method === 'KilledAccount' }) // Good practice to check for this before proceeding. expect(killedAccountEvent).toBeDefined() // This has the added benefit of performing an automatic typecast assert(baseClient.api.events.system.KilledAccount.is(killedAccountEvent!.event)) // This object will be annotated with type information from the runtime's // metadata regarding the event's fields - try it! const killedAccountEventData = killedAccountEvent!.event.data expect(killedAccountEventData.account.toString()).toBe(encodeAddress(alice.address, testConfig.addressEncoding)) ``` ### Events in snapshots In case the need is instead to snapshot a test's event rather than just inspecting it, consider the following example: ```typescript const transferTx = ... // For this to work, PJS' methods for sending transactions must be avoided in favor of chopstick's `sendTransaction`. const transferEvents = await sendTransaction(transferTx.signAsync(alice)) await checkEvents( transferEvents, // Event of fee withdrawal from Alice { section: 'balances', method: 'Withdraw' }, // Alice account is reaped, so dust is lost { section: 'balances', method: 'DustLost' }, ) // In case any numbers in the event are irrelevant or unstable, causing spurious test failures, they can be redacted. .redact({ number: 0 }) // Snapshots can be named, and each test can have many. This particular snapshot can be searched for by this name, and will be filed under the test's snapshot group. .toMatchSnapshot('unstable events when Alice `transfer_allow_death` to Bob') ``` Important points to remember: 1. `vitest` automatically creates snapshots, but does not automatically remove them - for this, pass `-u` to `yarn test` to remove/update snapshot files when running tests 2. snapshots should be included in the PR of a contribution to PET ## Errors ### Scrutinizing PJS errors See this for an example on how to catalyze a block's information for a failed extrinsic: https://polkadot.js.org/docs/api/cookbook/blocks/#how-do-i-determine-if-an-extrinsic-succeededfailed. ### Spurious `yarn test` errors locally/in CI At times, there may be spurious test failures caused by underlying infrastructure/network issues. Recent practical example: https://github.com/open-web3-stack/polkadot-ecosystem-tests/issues/161 Summary: in CI, errors such as `Internal RpcError: -32008: Response is too big: Exceeded max limit of ...` were caused by ([accidental](https://github.com/open-web3-stack/polkadot-ecosystem-tests/issues/161#issuecomment-2575665034)) changes in the configuration of RPC nodes. Always inspect test failures carefully, as their cause might be orthogonal to the test's object, and reveal an issue with the scenario that must be addressed. ## Miscellanous ### `vitest` snapshots If CI or local runs fail for a reason such as ``` Error: Error: Snapshot `Kusama Asset Hub Bounties > All bounty failure tests > Bounty cannot be awarded if it has an active child bounty > child bounty added events 1` mismatched ❯ Object.toMatchSnapshot packages/shared/src/helpers/index.ts:19:17 ❯ Checker.toMatchSnapshot node_modules/@acala-network/chopsticks-testing/dist/esm/check.js:240:51 ❯ hasActiveChildBountyTest packages/shared/src/bounties.ts:2886:3 ❯ testFn packages/shared/src/bounties.ts:3112:29 ``` and there is no more information regarding the actual cause of the snapshot mismatch, the data which caused it, or a diff, 1. check the snapshot filename in the local repository 2. check that filename in the PR's latest commit - test snapshots **must** be in the `git` tree 3. check the offending snapshot manually - it's possible some local change wasn't commited, and the latest PR's latest commit has an empty snapshot, so there's nothing to compare the test results to, and `undefined` can't be printed or diffed against ### PJS #### Executing `chopsticks` RPC calls in the JS console Where in a PET test in a TypeScript module, one would write ```js relayClient.api.<pallet_name>.<extrinsic_name>(..args) await paraClient.dev.newBlock() await relayClient.dev.setStorage({ Pallet: { storageItem: [ ... ], }, }) ``` in the JS console, it would instead be ```js api.<pallet_name>.<extrinsic_name>(..args) await api.rpc('dev_newBlock()', ..args) await api.rpc('dev_setStorage', { Pallet: { storageItem: [ ... ], }, }) ``` Documentation on these RPC methods, their arguments and how to call them: 1. [Acala's `chopsticks` docs](https://acalanetwork.github.io/chopsticks/docs/chopsticks/namespaces/DevRPC/README.html) 2. [Source code](https://github.com/AcalaNetwork/chopsticks/tree/master/packages/core/src/rpc/dev) See @seadanda's HackMD [below](#Additional-reading) for a more comprehensive set of techniques to be used when manually testing in PJS, which make use of the above RPC endpoints. #### PJS development accounts By default, when using `chopsticks` through `polkadot-js` in a browser, development accounts - Alice through Ferdie - use the [Sr25519](https://github.com/polkadot-js/apps/blob/fd8eeb556a16bce1d4f7b9db247b188614f6ad64/packages/test-support/src/keyring/signers.ts#L9) signature scheme, whereas PET'S [testing keypairs](https://github.com/open-web3-stack/polkadot-ecosystem-tests/blob/master/packages/networks/src/defaultAccounts.ts#L5) use [Ed25519](https://github.com/AcalaNetwork/chopsticks/blob/master/packages/utils/src/index.ts#L257) by default. Be mindful of this when `pause()`ing tests to then resume them manually in the browser, as `Alice` in the PET test will **not** be the same account as `Alice` in PJS. ### PR reviews When awaiting for comments or reviews from @xlc, the maintainer of PET, please note that Bryan is on UTC+12/13. Work on PET is best done async so as to not park yourself idle waiting for input. Furthermor, be mindful of times for meetings/DMs. ## Additional reading - Notes by @seadanda: {%preview https://hackmd.io/@seadanda/Sk3qcRlM0 %} - shows how to dispatch `Root` origin calls from relay chain to parachain using above `scheduler` method + XCM - showcases usefulness of `chopsticks` to debug/experiment - exemplifies runtime authorization/upgrade via PJS - Governance experiments by @xlc: {%preview https://hackmd.io/@xlc/H11BX5BLa %} - shows how to dispatch `Root` origin calls via the `scheduler` pallet

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully