Browse Source

Initial commit

refactor
Hampus Sjöberg 4 years ago
commit
36fd472103
  1. 6
      .gitignore
  2. 5
      .prettierrc
  3. 17
      README.md
  4. 36
      api.ts
  5. 40
      blocks.ts
  6. 51
      config/config.ts_TEMPLATE
  7. 1
      frontend-dev-server.sh
  8. 14
      frontend/app.tsx
  9. 1
      frontend/back
  10. 7
      frontend/components/Container.ts
  11. 7
      frontend/components/Content.ts
  12. 104
      frontend/pages/index.tsx
  13. 351
      frontend/style/reset.css
  14. 11
      frontend/style/site.css
  15. 29
      index.ts
  16. 49
      jsonrpc/index.ts
  17. 47
      jsonrpc/utils.ts
  18. 9
      run-server.sh

6
.gitignore

@ -0,0 +1,6 @@
.DS_Store
.vscode
.aleph
dist
config/config.ts

5
.prettierrc

@ -0,0 +1,5 @@
{
"tabWidth": 2,
"useTabs": false,
"printWidth": 120
}

17
README.md

@ -0,0 +1,17 @@
# Fork Explorer
Fork Explorer let's you see the status of a BIP9-style softfork. It relies on bitcoind and its JSON-RPC server.
# Build and run
You need [Deno](https://deno.land) to build and run this project. Deno is a new
Javascript environment, similar to Node.
0. Fix config file by duplicating `config/config.ts_TEMPLATE` to `config/config.ts` and setting
bitcoind's JSON-RPC credentials up.
1. `./run-server`.
2. Done.
# License
MIT

36
api.ts

@ -0,0 +1,36 @@
import { Router } from "https://deno.land/x/oak/mod.ts";
import { getBlocks } from "./blocks.ts";
import { getblockchaininfo, getblockcount, getblockhash } from "./jsonrpc/index.ts";
const router = new Router();
router
.get("/getblockchaininfo", async (context) => {
const info = await getblockchaininfo();
console.log(info);
context.response.body = info;
})
.get<{
height: string;
}>("/getblockhash/:height", async (context) => {
if (!context.params || !context.params.height) {
context.response.status = 400;
context.response.body = JSON.stringify({
status: "ERROR",
reason: "Missing param height",
});
}
const blockhash = await getblockhash(Number.parseInt(context.params.height));
context.response.body = blockhash;
})
.get("/getblockcount", async (context) => {
const blockCount = await getblockcount();
context.response.body = blockCount;
});
router.get("/blocks", (context) => {
const blocks = getBlocks();
context.response.body = blocks;
});
export default router;

40
blocks.ts

@ -0,0 +1,40 @@
import { getblockcount, getblockhash, getblockheader } from "./jsonrpc/index.ts";
import config from "./config/config.ts";
export interface IBlock {
height: number;
// hash: string;
signals: boolean;
}
const blocks: IBlock[] = [];
export async function bootstrapBlocks() {
console.log("Bootstrapping block data...");
const blockCount = await getblockcount();
const startHeight = blockCount - 1000;
console.log(`Current block height is ${blockCount}`);
for (let i = startHeight; i < blockCount; i++) {
try {
const blockHash = await getblockhash(i);
const blockheader = await getblockheader(blockHash);
blocks.push({
height: blockheader.height,
// hash: blockheader.hash,
signals: (blockheader.version & (config.fork.versionBit + 1)) === config.fork.versionBit + 1,
});
} catch (error) {
console.error("Block boostrapping failed");
throw error;
}
}
console.log("Done.");
}
export function getBlocks() {
return blocks;
}

51
config/config.ts_TEMPLATE

@ -0,0 +1,51 @@
interface Config {
// Configuration related to the API server
server: {
// The server host or listening IP
host: string;
// The server listening port
port: number;
};
// Configuration for bitcoind's JSON-RPC server
bitcoinRpc: {
// Server host IP or domain.
server: string;
// Username credentials.
user: string;
// Password credentials.
password: string;
};
// Information about the softfork in question should be added here.
// Things inside here will most likely be used and shown on the webpage.
fork: {
// The common name of this softfork.
name: string;
// Information about this softfork, each array item is rendered as a paragraph.
info: string[];
// The BIP9 version bit as defined in the softfork's BIP.
versionBit: number;
};
}
const config: Config = {
server: {
host: "127.0.0.1",
port: 8080,
},
bitcoinRpc: {
server: "http://127.0.0.1:8332",
user: "",
password: "",
},
fork: {
name: "Taproot",
info: [],
versionBit: 2,
},
};
export default config;

1
frontend-dev-server.sh

@ -0,0 +1 @@
deno run -A https://deno.land/x/aleph@v0.3.0-alpha.32/cli.ts dev frontend

14
frontend/app.tsx

@ -0,0 +1,14 @@
import React, { ComponentType } from "https://esm.sh/react@17.0.2";
export default function App({ Page, pageProps }: { Page: ComponentType<any>; pageProps: any }) {
return (
<main style={{ width: "100%", height: "100%" }}>
<head>
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet" href="./style/site.css" />
<link rel="stylesheet" href="./style/reset.css" />
</head>
<Page {...pageProps} />
</main>
);
}

1
frontend/back

@ -0,0 +1 @@
..

7
frontend/components/Container.ts

@ -0,0 +1,7 @@
import styled from "https://esm.sh/styled-components";
export const Container = styled.div`
min-height: 100%;
display: flex;
justify-content: center;
`;

7
frontend/components/Content.ts

@ -0,0 +1,7 @@
import styled from "https://esm.sh/styled-components";
export const Content = styled.div`
flex-direction: row;
width: 80%;
max-width: 1246px;
`;

104
frontend/pages/index.tsx

@ -0,0 +1,104 @@
import React, { useEffect, useState } from "https://esm.sh/react@17.0.2";
import styled from "https://esm.sh/styled-components";
import config from "../back/config/config.ts";
import { IBlock } from "../back/blocks.ts";
import { Container } from "../components/Container.ts";
import { Content } from "../components/Content.ts";
const Title = styled.h1`
margin-top: 85px;
font-size: 42px;
text-align: center;
color: #d97b08;
text-shadow: #000 3px 3px 0px;
`;
const DescriptionBlock = styled.div`
max-width: 600px;
margin: auto;
`;
const Text = styled.p`
color: #f7f7f7;
/* text-shadow: #000 2px 2px 0px; */
font-size: 16px;
text-align: center;
`;
const LastXBlocks = styled.h2`
font-size: 24px;
margin-bottom: 10px;
color: #ff9b20;
text-shadow: #000 3px 3px 0px;
`;
const BlockContainer = styled.div`
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
padding: 3px;
background-color: #434343;
box-shadow: #000 3px 3px 14px;
border-radius: 6px;
margin-bottom: 30px;
`;
const BlockStyle = styled.div<{ signals?: boolean }>`
background: ${(props) =>
props.signals
? "linear-gradient(45deg, rgba(18,209,0,1) 0%, rgba(9,89,0,1) 100%)"
: "linear-gradient(45deg, rgba(209,0,0,1) 0%, rgba(89,0,0,1) 100%)"};
width: 20px;
height: 20px;
margin: 3px;
border-radius: 4px;
`;
interface IBlockProps {
height: number;
signals: boolean;
}
function Block({ height, signals }: IBlockProps) {
return <BlockStyle title={`Height: ${height}`} signals={signals}></BlockStyle>;
}
export default function Blocks() {
const [blocks, setBlocks] = useState<IBlock[] | undefined>(undefined);
useEffect(() => {
(async () => {
const result = await fetch("/blocks");
const json = (await result.json()) as IBlock[];
console.log(json);
setBlocks(json);
})();
}, []);
if (blocks === undefined) {
return <></>;
}
return (
<Container>
<head>
<title>{config.fork.name} activation</title>
</head>
<Content>
<Title>{config.fork.name} activation</Title>
<DescriptionBlock>
{config.fork.info.map((text, index) => (
<Text key={index}>{text}</Text>
))}
</DescriptionBlock>
<LastXBlocks>Last {Math.min(1000, blocks.length)} blocks</LastXBlocks>
<BlockContainer>
{blocks.map((block, i) => (
<Block key={i} height={block.height} signals={block.signals} />
))}
</BlockContainer>
</Content>
</Container>
);
}

351
frontend/style/reset.css

@ -0,0 +1,351 @@
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input {
/* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select {
/* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}

11
frontend/style/site.css

@ -0,0 +1,11 @@
html,
body,
#__aleph {
min-height: 100%;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol";
background-color: #242424;
}

29
index.ts

@ -0,0 +1,29 @@
import { Application, send } from "https://deno.land/x/oak@v7.3.0/mod.ts";
import config from "./config/config.ts";
import router from "./api.ts";
import { bootstrapBlocks } from "./blocks.ts";
await bootstrapBlocks();
const app = new Application();
app.use(router.routes());
app.use(async (context) => {
await send(context, context.request.url.pathname, {
root: `${Deno.cwd()}/frontend/dist`,
index: "index.html",
});
});
app.addEventListener("listen", ({ hostname, port, secure }) => {
const host = hostname ?? "localhost";
const protocol = secure ? "https://" : "http://";
console.log(`Listening on: ${protocol}${host}:${port}`);
});
await app.listen({
hostname: config.server.host,
port: config.server.port,
});

49
jsonrpc/index.ts

@ -0,0 +1,49 @@
import { request } from "./utils.ts";
export function getblockchaininfo() {
return request("getblockchaininfo", []);
}
type IGetblockcountResponse = number;
export async function getblockcount() {
const response = await request("getblockcount", []);
if (response.error) {
throw new Error(response.error.message);
}
return response.result as IGetblockcountResponse;
}
type IGetblockhashResponse = string;
export async function getblockhash(height: number) {
const response = await request("getblockhash", [height]);
if (response.error) {
throw new Error(response.error.message);
}
return response.result as IGetblockhashResponse;
}
interface IGetblockheaderResponse {
hash: string;
confirmations: number;
height: number;
version: number;
versionHex: string;
merkleroot: string;
time: number;
mediantime: number;
nonce: number;
bits: string;
difficulty: number;
chainwork: string;
nTx: number;
previousblockhash: string;
nextblockhash: string;
}
export async function getblockheader(hash: string) {
const response = await request("getblockheader", [hash, true]);
if (response.error) {
throw new Error(response.error.message);
}
return (response.result as unknown) as IGetblockheaderResponse;
}

47
jsonrpc/utils.ts

@ -0,0 +1,47 @@
import * as base64 from "https://denopkg.com/chiefbiiko/base64/mod.ts";
import config from "../config/config.ts";
const host = config.bitcoinRpc.server;
const rpcUser = config.bitcoinRpc.user;
const rpcPassword = config.bitcoinRpc.password;
const credentials: string = base64.fromUint8Array(new TextEncoder().encode(`${rpcUser}:${rpcPassword}`));
const headers = new Headers();
headers.set("Authorization", "Basic " + credentials);
// https://github.com/microsoft/TypeScript/issues/1897#issuecomment-338650717
export interface JsonMap {
[key: string]: AnyJson;
}
// deno-lint-ignore no-empty-interface
export interface JsonArray extends Array<AnyJson> {}
export type AnyJson = boolean | number | string | null | JsonArray | JsonMap;
interface RpcJsonResponse {
result: AnyJson;
error: null;
id?: string;
}
interface RpcJsonError {
result: null;
error: {
code: string;
message: string;
};
id?: string;
}
export async function request(method: string, data: unknown[]): Promise<RpcJsonResponse | RpcJsonError> {
const result = await fetch(host, {
headers,
method: "POST",
body: JSON.stringify({
jsonrpc: "1.0",
method: method,
params: data,
}),
});
return await result.json();
}

9
run-server.sh

@ -0,0 +1,9 @@
#!/bin/bash
# Build frontend
rm -r frontend/dist 2>/dev/null
mkdir frontend/dist
deno run -A https://deno.land/x/aleph@v0.3.0-alpha.32/cli.ts build frontend --reload
# Run backend
deno run --allow-net --allow-read index.ts
Loading…
Cancel
Save