Skip to content

Commit

Permalink
#104 Additional AUXPOW merged-mining code from Namecoin.
Browse files Browse the repository at this point in the history
  • Loading branch information
develCuy committed Jun 15, 2022
1 parent a5a6593 commit ed83fdb
Show file tree
Hide file tree
Showing 30 changed files with 887 additions and 306 deletions.
14 changes: 14 additions & 0 deletions TODO
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
* upgrade to new version at fixed height:
- P2SH as enforced soft fork (BIP16)
- strict BIP30 as enforced soft fork
- allow larger blocks (DB locks)
- no need to disallow auxpow parent blocks with auxpow-flag in the version
any more; instead, the auxpow is simply never loaded for them
- disallow legacy blocks also on testnet
- restrict auxpow size / coinbase tx size?

* reenable some of the disabled tests, new alert keys?

* make dust spam unspendable?

* simplify regtests with reduced number of nodes?
112 changes: 112 additions & 0 deletions contrib/auxpow-sizes/auxpow-sizes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env python

# Auxpow Sizes - find sizes of auxpow's in the blockchain
# Copyright (C) 2015 Daniel Kraft <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import jsonrpc
import sys
import urllib

username = urllib.quote_plus ("devcoin")
password = urllib.quote_plus ("password")
port = 8336
url = "http://%s:%s@localhost:%d/" % (username, password, port)

class AuxpowStats:
"""
Keep track of the interesting statistics of the auxpows
found in the blockchain.
"""

def __init__ (self):
self.merkleLength = dict ()
self.txSize = dict ()
self.maxMerkle = 0
self.maxTxSize = 0

def add (self, obj):
"""
Add the auxpow described by the block JSON obj (if any)
to the statistics.
"""

if 'auxpow' not in obj:
return

txSize = len (obj['auxpow']['tx']['hex']) / 2
merkleLen = len (obj['auxpow']['merklebranch'])

if txSize not in self.txSize:
self.txSize[txSize] = 1
else:
self.txSize[txSize] += 1

if merkleLen not in self.merkleLength:
self.merkleLength[merkleLen] = 1
else:
self.merkleLength[merkleLen] += 1

if txSize > self.maxTxSize:
self.maxTxSize = txSize
self.maxTxSizeHash = obj['hash']
if merkleLen > self.maxMerkle:
self.maxMerkle = merkleLen
self.maxMerkleHash = obj['hash']

def output (self):
"""
Output statistics in the end.
"""

print "Merkle lengths:"
for (key, val) in self.merkleLength.items ():
print "%4d: %6d" % (key, val)
print "Maximum: %d, block %s\n" % (self.maxMerkle, self.maxMerkleHash)

print "\nCoinbase tx sizes:"
buckets = [0, 1000, 2000, 5000, 10000, 20000, 50000]
bucketCnts = (len (buckets) + 1) * [0]
for (key, val) in self.txSize.items ():
for i in range (len (buckets) - 1, -1, -1):
if (key >= buckets[i]):
bucketCnts[i] += val
for i in range (len (buckets) - 1):
label = "%d - %d" % (buckets[i], buckets[i + 1] - 1)
print " %15s: %6d" % (label, bucketCnts[i])
label = ">= %d" % buckets[-1]
print " %15s: %6d" % (label, bucketCnts[-1])
print "Maximum: %d, block %s\n" % (self.maxTxSize, self.maxTxSizeHash)

rpc = jsonrpc.proxy.ServiceProxy (url)
tips = rpc.getchaintips ()
tip = None
for t in tips:
if t['status'] == 'active':
tip = t
break
assert tip is not None

stats = AuxpowStats ()
curHash = tip['hash']
while True:
obj = rpc.getblock (curHash)
stats.add (obj)
if obj['height'] % 1000 == 0:
sys.stderr.write ("At height %d...\n" % obj['height'])
if 'previousblockhash' not in obj:
break
curHash = obj['previousblockhash']
stats.output ()
124 changes: 124 additions & 0 deletions contrib/auxpow/getwork-wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/usr/bin/env python
# Copyright (c) 2018 Daniel Kraft
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

# This is a simple wrapper around the merge-mining interface of the core
# daemon (createauxblock and submitauxblock). It handles the creation of
# a "fake" auxpow, providing an external miner with a getwork-like interface.
#
# Since using this loses the ability to *actually* merge mine with a parent
# chain, this is likely not very useful in production. But it can be used
# for testing and debugging.
#
# This needs jsonrpclib, which can be found in the 'python-jsonrpclib' Debian
# package or at https://github.com/joshmarshall/jsonrpclib. It also imports
# auxpow from test/functional/test_framework.

import codecs
import optparse
import struct
from xmlrpclib import ProtocolError

import jsonrpclib
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer

import auxpow


class GetworkWrapper:
"""
The main server class. It sets up a JSON-RPC server and handles requests
coming in.
"""

def __init__ (self, backend, host, port):
self.backend = backend
self.server = SimpleJSONRPCServer ((host, port))

def getwork (data=None):
if data is None:
return self.createWork ()
return self.submitWork (data)
self.server.register_function (getwork)

# We use our own extra nonce to not return the same work twice if
# asked again for new work.
self.extraNonce = 0

# Dictionary that holds all created works so they can be retrieved when
# necessary for matching. The key is the (byte-order swapped) block merkle
# hash, which is not changed by the miner when processing the work.
# This is cleared only once a submitted block was accepted. We do not try
# to detect if the chain tip changes externally.
self.works = {}

def keyForWork (self, data):
"""
Returns the key used in self.works for the given, hex-encoded and
byte-swapped, getwork 'data'.
"""

return data[2*36 : 2*68]

def createWork (self):
auxblock = self.backend.getauxblock ()
(tx, hdr) = auxpow.constructAuxpow (auxblock['hash'])

en = self.extraNonce
self.extraNonce = (self.extraNonce + 1) % (1 << 32)

hdrBytes = bytearray (codecs.decode (hdr, 'hex_codec'))
hdrBytes[0:4] = struct.pack ('<I', en)
formatted = auxpow.getworkByteswap (hdrBytes)
formatted += bytearray ([0] * (128 - len (formatted)))
formatted[83] = 0x80
formatted[-4] = 0x80
formatted[-3] = 0x02
work = codecs.encode (formatted, 'hex_codec')

self.works[self.keyForWork (work)] = {"auxblock": auxblock, "tx": tx}

return {"data": work, "target": auxblock['_target']}

def submitWork (self, data):
key = self.keyForWork (data)
if not key in self.works:
print ('Error: stale / unknown work submitted')
return False
w = self.works[key]

dataBytes = codecs.decode (data, 'hex_codec')
fixedBytes = auxpow.getworkByteswap (dataBytes[:80])
hdrHex = codecs.encode (fixedBytes, 'hex_codec')

auxpowHex = auxpow.finishAuxpow (w['tx'], hdrHex)
try:
res = self.backend.submitauxblock (w['auxblock']['hash'], auxpowHex)
except ProtocolError as exc:
print ('Error submitting work: %s' % exc)
return False

# Clear cache of created works when a new block was accepted.
if res:
self.works = {}

return res

def serve (self):
self.server.serve_forever ()


if __name__ == '__main__':
parser = optparse.OptionParser (usage="%prog [options]")
parser.add_option ("--backend-url", dest="backend",
help="URL for the backend daemon connection")
parser.add_option ("--port", dest="port", type="int",
help="Port on which to serve")
(options, _) = parser.parse_args ()
if options.backend is None or options.port is None:
parser.error ("--backend-url and --port must be specified")

backend = jsonrpclib.Server (options.backend)
server = GetworkWrapper (backend, 'localhost', options.port)
server.serve ()
13 changes: 13 additions & 0 deletions contrib/auxpow/getwork-wrapper.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
# Copyright (c) 2018 Daniel Kraft
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

# Starts up the getwork-wrapper.py script, setting PYTHONPATH accordingly.
# This script must be run from the root source directory to work correctly.
#
# Example:
# contrib/auxpow/getwork-wrapper.sh http://user:pass@localhost:port/ 1234

PYTHONPATH="test/functional/test_framework"
contrib/auxpow/getwork-wrapper.py --backend-url="$1" --port="$2"
1 change: 1 addition & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ DEVCOIN_CORE_H = \
rpc/mining.h \
rpc/net.h \
rpc/protocol.h \
rpc/rawtransaction.h \
rpc/rawtransaction_util.h \
rpc/register.h \
rpc/request.h \
Expand Down
29 changes: 12 additions & 17 deletions src/auxpow.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2011 Vince Durham
// Copyright (c) 2009-2014 The Bitcoin developers
// Copyright (c) 2014-2019 Daniel Kraft
// Copyright (c) 2014-2021 Daniel Kraft
// Distributed under the MIT/X11 software license, see the accompanying
// file license.txt or http://www.opensource.org/licenses/mit-license.php.

Expand Down Expand Up @@ -42,22 +42,17 @@ CAuxPow::check (const uint256& hashAuxBlock, int nChainId,
const Consensus::Params& params) const
{
if (params.fStrictChainId) {
const int32_t &nChainIDParent = parentBlock.GetChainId();
if(nChainIDParent > 0) {
if(nChainIDParent == params.nAuxpowChainId)
return error("Aux POW parent has our chain ID");
} else {
const int32_t &nOldChainIDParent = parentBlock.GetOldChainId();
if(nOldChainIDParent == params.nAuxpowOldChainId)
return error("Aux POW parent has our old chain ID");
}
if (parentBlock.GetChainId () == nChainId)
return error("Aux POW parent has our chain ID");
else if(parentBlock.GetOldChainId() == params.nAuxpowOldChainId)
return error("Aux POW parent has our old chain ID");
}

if (vChainMerkleBranch.size() > 30)
return error("Aux POW chain merkle branch too long");

// Check that the chain merkle root is in the coinbase
const uint256 &nRootHash
const uint256 nRootHash
= CheckMerkleBranch (hashAuxBlock, vChainMerkleBranch, nChainIndex);
valtype vchRootHash(nRootHash.begin (), nRootHash.end ());
std::reverse (vchRootHash.begin (), vchRootHash.end ()); // correct endian
Expand All @@ -71,7 +66,7 @@ CAuxPow::check (const uint256& hashAuxBlock, int nChainId,
if (coinbaseTx->vin.empty())
return error("Aux POW coinbase has no inputs");

const CScript &script = coinbaseTx->vin[0].scriptSig;
const CScript script = coinbaseTx->vin[0].scriptSig;

// Check that the same work is not submitted twice to our chain.
//
Expand Down Expand Up @@ -114,21 +109,21 @@ CAuxPow::check (const uint256& hashAuxBlock, int nChainId,
if (script.end() - pc < 8)
return error("Aux POW missing chain merkle tree size and nonce in parent coinbase");

const uint32_t &nSize = DecodeLE32 (&pc[0]);
const unsigned &merkleHeight = vChainMerkleBranch.size ();
const uint32_t nSize = DecodeLE32 (&pc[0]);
const unsigned merkleHeight = vChainMerkleBranch.size ();
if (nSize != (1u << merkleHeight))
return error("Aux POW merkle branch size does not match parent coinbase");

const uint32_t &nNonce = DecodeLE32 (&pc[4]);
const uint32_t nNonce = DecodeLE32 (&pc[4]);
if (nChainIndex != getExpectedIndex (nNonce, nChainId, merkleHeight))
return error("Aux POW wrong index");

return true;
}

int
CAuxPow::getExpectedIndex (const uint32_t &nNonce, const int &nChainId,
const unsigned &h)
CAuxPow::getExpectedIndex (const uint32_t nNonce, const int nChainId,
const unsigned h)
{
// Choose a pseudo-random slot in the chain merkle tree
// but have it be fixed for a size/nonce/chain combination.
Expand Down
Loading

0 comments on commit ed83fdb

Please sign in to comment.