Skip to content

Commit

Permalink
Merge a48578c into 292e860
Browse files Browse the repository at this point in the history
  • Loading branch information
cculianu committed Aug 17, 2020
2 parents 292e860 + a48578c commit f1d02b9
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 5 deletions.
156 changes: 156 additions & 0 deletions lib/asert_daa.py
@@ -0,0 +1,156 @@
# Electron Cash - lightweight Bitcoin Cash client
# Copyright (C) 2020 The Electron Cash Developers
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import os

from collections import namedtuple
from typing import Optional, Union

from .util import print_error

def bits_to_target(bits: int) -> int:
size = bits >> 24
assert size <= 0x1d

word = bits & 0x00ffffff
assert 0x8000 <= word <= 0x7fffff

if size <= 3:
return word >> (8 * (3 - size))
else:
return word << (8 * (size - 3))

def _get_asert_activation_mtp():
""" Returns 1605441600 (Nov 15, 2020 12:00:00 UTC) or whatever override may
be set by the env variable ASERT_MTP """
default_mtp = 1605441600 # Nov 15, 2020 12:00:00 UTC
mtp = os.environ.get('ASERT_MTP', default_mtp)
try: mtp = int(mtp)
except: pass
if not isinstance(mtp, int) or mtp <= 1510600000:
print_error("Error: Environment variable ASERT_MTP ignored because it is invalid: {}".format(str(mtp)))
mtp = default_mtp
if mtp != default_mtp:
print_error("ASERT_MTP of {} will be used".format(mtp))
return mtp

class Anchor(namedtuple("Anchor", "height bits prev_time")):
pass

class ASERTDaa:
""" Parameters and methods for the ASERT DAA. Instances of these live in
networks.TestNet, networks.MainNet as part of the chain params. """

MTP_ACTIVATION_TIME = _get_asert_activation_mtp() # Normally Nov. 15th, 2020 UTC 12:00:00

IDEAL_BLOCK_TIME = 10 * 60 # 10 mins
HALF_LIFE = 2 * 24 * 3600 # for mainnet, testnet has 3600 (1 hour) half-life
# Integer implementation uses these for fixed point math
RBITS = 16 # number of bits after the radix for fixed-point math
RADIX = 1 << RBITS
# POW Limit
MAX_BITS = 0x1d00ffff

MAX_TARGET = bits_to_target(MAX_BITS)

anchor: Optional[Anchor] = None

def __init__(self, is_testnet=False):
if is_testnet:
# From ASERT spec, testnet has 1 hour half-life
self.HALF_LIFE = 3600

@staticmethod
def bits_to_target(bits: int) -> int: return bits_to_target(bits)

def target_to_bits(self, target: int) -> int:
assert target > 0
if target > self.MAX_TARGET:
print_error('Warning: target went above maximum ({} > {})'.format(target, self.MAX_TARGET))
target = self.MAX_TARGET
size = (target.bit_length() + 7) // 8
mask64 = 0xffffffffffffffff
if size <= 3:
compact = (target & mask64) << (8 * (3 - size))
else:
compact = (target >> (8 * (size - 3))) & mask64

if compact & 0x00800000:
compact >>= 8
size += 1

assert compact == (compact & 0x007fffff)
assert size < 256
return compact | size << 24

@staticmethod
def bits_to_work(bits: int) -> int:
return (2 << 255) // (bits_to_target(bits) + 1)

@staticmethod
def target_to_hex(target: int) -> str:
h = hex(target)[2:]
return '0' * (64 - len(h)) + h

def next_bits_aserti3_2d(self, anchor_bits: int, time_diff: Union[float, int], height_diff: int) -> int:
""" Integer ASERTI algorithm, based on Jonathan Toomim's
`next_bits_aserti` implementation in mining.py (see
https://github.com/jtoomim/difficulty) """

target = self.bits_to_target(anchor_bits)

# Ultimately, we want to approximate the following ASERT formula, using
# only integer (fixed-point) math:
# new_target = old_target * 2^((time_diff -
# IDEAL_BLOCK_TIME*(height_diff+1)) / HALF_LIFE)

# First, we'll calculate the exponent, using floor division. The
# assertion checks a type constraint of the C++ implementation which
# uses a 64-bit signed integer for the exponent. If inputs violate that,
# then the implementation will diverge.
assert(abs(time_diff - self.IDEAL_BLOCK_TIME * (height_diff+1)) < (1<<(63-self.RBITS)))
exponent = int(((time_diff - self.IDEAL_BLOCK_TIME*(height_diff+1)) * self.RADIX) / self.HALF_LIFE)

# Next, we use the 2^x = 2 * 2^(x-1) identity to shift our exponent into the (0, 1] interval.
shifts = exponent >> self.RBITS
exponent -= shifts * self.RADIX
assert(exponent >= 0 and exponent < 65536)

# Now we compute an approximated target * 2^(fractional part) * 65536
# target * 2^x ~= target * (1 + 0.695502049*x + 0.2262698*x**2 + 0.0782318*x**3)
target *= self.RADIX + ((195766423245049*exponent + 971821376*exponent**2 + 5127*exponent**3 + 2**47)>>(self.RBITS*3))

# Next, we shift to multiply by 2^(integer part). Python doesn't allow
# shifting by negative integers, so:
if shifts < 0:
target >>= -shifts
else:
target <<= shifts
# Remove the 65536 multiplier we got earlier
target >>= self.RBITS

if target == 0:
return self.target_to_bits(1)
if target > self.MAX_TARGET:
return self.MAX_BITS

return self.target_to_bits(target)
62 changes: 57 additions & 5 deletions lib/blockchain.py
Expand Up @@ -25,9 +25,12 @@
import sys
import threading

from typing import Optional

from . import util
from . import asert_daa
from . import networks
from . import util

from .bitcoin import *

class VerifyError(Exception):
Expand Down Expand Up @@ -420,6 +423,37 @@ def get_suitable_block_height(self, suitableheight, chunk=None):

return blocks1['block_height']

_cached_asert_anchor: Optional[asert_daa.Anchor] = None # cached Anchor, per-Blockchain instance
def get_asert_anchor(self, prevheader, mtp, chunk=None):
if networks.net.asert_daa.anchor is not None:
# Checkpointed (hard-coded) value exists, just use that
return networks.net.asert_daa.anchor
if (self._cached_asert_anchor is not None
and self._cached_asert_anchor.height <= prevheader['block_height']):
return self._cached_asert_anchor
# ****
# This may be slow -- we really should be leveraging the hard-coded
# checkpointed value. TODO: add hard-coded value to networks.py after
# Nov. 15th 2020 HF to ASERT DAA
# ****
anchor = prevheader
activation_mtp = networks.net.asert_daa.MTP_ACTIVATION_TIME
while mtp >= activation_mtp:
ht = anchor['block_height']
prev = self.read_header(ht - 1, chunk)
if prev is None:
self.print_error("get_asert_anchor missing header {}".format(ht - 1))
return None
prev_mtp = self.get_median_time_past(ht - 1, chunk)
if prev_mtp < activation_mtp:
# Ok, use this as anchor -- since it is the first in the chain
# after activation.
bits = anchor['bits']
self._cached_asert_anchor = asert_daa.Anchor(ht, bits, prev['timestamp'])
return self._cached_asert_anchor
mtp = prev_mtp
anchor = prev

def get_bits(self, header, chunk=None):
'''Return bits for the given height.'''
# Difficulty adjustment interval?
Expand All @@ -433,13 +467,31 @@ def get_bits(self, header, chunk=None):
raise Exception("get_bits missing header {} with chunk {!r}".format(height - 1, chunk))
bits = prior['bits']

#NOV 13 HF DAA
# NOV 13 HF DAA and/or ASERT DAA

prevheight = height -1
prevheight = height - 1
daa_mtp = self.get_median_time_past(prevheight, chunk)

#if (daa_mtp >= 1509559291): #leave this here for testing
if (daa_mtp >= 1510600000):

# ASERTi3-2d DAA activated on Nov. 15th 2020 HF
if daa_mtp >= networks.net.asert_daa.MTP_ACTIVATION_TIME:
header_ts = header['timestamp']
prev_ts = prior['timestamp']
if networks.net.TESTNET:
# testnet 20 minute rule
if header_ts - prev_ts > 20*60:
return MAX_BITS

anchor = self.get_asert_anchor(prior, daa_mtp, chunk)
assert anchor is not None, "Failed to find ASERT anchor block for chain {!r}".format(self)

return networks.net.asert_daa.next_bits_aserti3_2d(anchor.bits,
prev_ts - anchor.prev_time,
prevheight - anchor.height)


# Mon Nov 13 19:06:40 2017 DAA HF
if daa_mtp >= 1510600000:

if networks.net.TESTNET:
# testnet 20 minute rule
Expand Down
4 changes: 4 additions & 0 deletions lib/networks.py
Expand Up @@ -24,6 +24,8 @@

import json, pkgutil

from .asert_daa import ASERTDaa

def _read_json_dict(filename):
try:
data = pkgutil.get_data(__name__, filename)
Expand All @@ -34,6 +36,7 @@ def _read_json_dict(filename):

class AbstractNet:
TESTNET = False
asert_daa = ASERTDaa()


class MainNet(AbstractNet):
Expand Down Expand Up @@ -78,6 +81,7 @@ class MainNet(AbstractNet):

class TestNet(AbstractNet):
TESTNET = True
asert_daa = ASERTDaa(is_testnet=True)
WIF_PREFIX = 0xef
ADDRTYPE_P2PKH = 111
ADDRTYPE_P2PKH_BITPAY = 111 # Unsure
Expand Down

0 comments on commit f1d02b9

Please sign in to comment.