File size: 2,908 Bytes
931572e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#!/usr/bin/env node
import { resolve } from "node:path";
import { Mempool } from "./mempool.mjs";
import { P2PNode } from "./p2p.mjs";
import { JsonRpcServer } from "./rpc.mjs";
import { SnapKittyChain } from "./chain.mjs";

const args = parseArgs(process.argv.slice(2));
const dataDir = resolve(args.data || ".snapkitty-chain");
const producer = args.producer || "SNAPKITTY-VALIDATOR";
const mempool = new Mempool();
const chain = new SnapKittyChain({
  dataDir,
  difficulty: Number(args.difficulty || 2),
  genesisProducer: producer
});

let p2p;

function produceBlock(source = "miner") {
  const result = chain.mineBlock(`${producer}:${source}`, mempool.sorted());
  if (result.ok) {
    mempool.removeMany(result.block.transactions.map((tx) => tx.hash));
    p2p?.broadcastBlock(result.block);
    log(`block ${result.block.height} ${result.block.hash.slice(0, 16)} txs=${result.block.transactions.length}`);
  } else {
    log(`block rejected ${result.reason}`);
  }
  return result;
}

function onTx(tx, remote = false) {
  const result = mempool.add(tx);
  if (result.accepted && !remote) p2p?.broadcastTx(result.tx);
  if (result.accepted) log(`tx ${result.tx.hash.slice(0, 16)} mempool=${mempool.size()}`);
  return result;
}

function onBlock(block) {
  const result = chain.acceptBlock(block);
  if (result.ok) {
    mempool.removeMany(block.transactions.map((tx) => tx.hash));
    log(`accepted peer block ${block.height} ${block.hash.slice(0, 16)}`);
  }
  return result;
}

p2p = new P2PNode({
  port: Number(args.p2p || 30333),
  chain,
  mempool,
  onBlock,
  onTx
});

const rpc = new JsonRpcServer({
  port: Number(args.rpc || 8545),
  chain,
  mempool,
  produceBlock,
  peers: () => p2p.peers()
});

await p2p.listen();
await rpc.listen();

for (const peer of arrayArg(args.peer)) {
  p2p.connect(peer);
}

log(`rpc=http://127.0.0.1:${args.rpc || 8545} p2p=127.0.0.1:${args.p2p || 30333}`);
log(`data=${dataDir}`);
log(`head=${chain.height()} ${chain.head().hash}`);

if (args.mine) {
  setInterval(() => {
    if (mempool.size() > 0 || args.emptyBlocks) produceBlock("miner");
  }, Number(args.blockTime || 5000));
}

function parseArgs(argv) {
  const out = {};
  for (let i = 0; i < argv.length; i += 1) {
    const arg = argv[i];
    if (!arg.startsWith("--")) continue;
    const key = arg.slice(2);
    const next = argv[i + 1];
    if (!next || next.startsWith("--")) {
      out[key] = true;
    } else if (out[key]) {
      out[key] = Array.isArray(out[key]) ? [...out[key], next] : [out[key], next];
      i += 1;
    } else {
      out[key] = next;
      i += 1;
    }
  }
  return out;
}

function arrayArg(value) {
  if (!value) return [];
  return Array.isArray(value) ? value : [value];
}

function log(message) {
  console.log(`[SNAPKITTY-CHAIN] ${message}`);
}