Files
stacks-puppet-node/blockstack_client/constants.py

473 lines
17 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
"""
Blockstack-client
~~~~~
copyright: (c) 2014 by Halfmoon Labs, Inc.
copyright: (c) 2015 by Blockstack.org
This file is part of Blockstack-client.
Blockstack-client is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Blockstack-client 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Blockstack-client. If not, see <http://www.gnu.org/licenses/>.
"""
import os
import json
import tempfile
import subprocess
import fcntl
import virtualchain
from .version import __version__, __version_major__, __version_minor__, __version_patch__
BLOCKSTACK_TEST = os.environ.get('BLOCKSTACK_TEST', None)
BLOCKSTACK_TEST_NODEBUG = os.environ.get('BLOCKSTACK_TEST_NODEBUG', None)
BLOCKSTACK_DEBUG = os.environ.get('BLOCKSTACK_DEBUG', None)
BLOCKSTACK_TEST_FIRST_BLOCK = os.environ.get('BLOCKSTACK_TEST_FIRST_BLOCK', None)
BLOCKSTACK_TESTNET = os.environ.get("BLOCKSTACK_TESTNET", None)
BLOCKSTACK_TESTNET3 = os.environ.get("BLOCKSTACK_TESTNET3", None)
BLOCKSTACK_TESTNET_FIRST_BLOCK = os.environ.get("BLOCKSTACK_TESTNET_FIRST_BLOCK", None)
BLOCKSTACK_DRY_RUN = os.environ.get('BLOCKSTACK_DRY_RUN', None)
if BLOCKSTACK_DRY_RUN is not None:
BLOCKSTACK_DRY_RUN = True
DEBUG = False
if BLOCKSTACK_TEST is not None and BLOCKSTACK_TEST_NODEBUG is None:
DEBUG = True
if BLOCKSTACK_DEBUG is not None:
DEBUG = True
if os.environ.get("DISABLE_CLIENT_DEBUG") == "1":
DEBUG = False
TX_MIN_CONFIRMATIONS = 6
if os.environ.get("BLOCKSTACK_TEST", None) is not None:
# test environment
TX_MIN_CONFIRMATIONS = 0
VERSION = __version__
SERIES_VERSION = "{}.{}.{}".format(__version_major__, __version_minor__, __version_patch__)
DEFAULT_BLOCKSTACKD_PORT = 6264 # blockstack indexer port
DEFAULT_BLOCKSTACKD_SERVER = 'node.blockstack.org'
DEFAULT_API_PORT = 6270 # API endpoint port
LOG_NETWORK_PORT = 8333 # port to send log messages on (e.g. to Portal)
# initialize to default settings
BLOCKSTACKD_SERVER = DEFAULT_BLOCKSTACKD_SERVER
BLOCKSTACKD_PORT = DEFAULT_BLOCKSTACKD_PORT
WALLET_PASSWORD_LENGTH = 8
WALLET_DECRYPT_MAX_TRIES = 5
WALLET_DECRYPT_BACKOFF_RESET = 3600
BLOCKSTACK_DEFAULT_STORAGE_DRIVERS = 'disk,blockstack_resolver,blockstack_server,http,dht'
# storage drivers that must successfully acknowledge each write
BLOCKSTACK_REQUIRED_STORAGE_DRIVERS_WRITE = 'disk,blockstack_server,dht'
DEFAULT_TIMEOUT = 30 # in secs
""" transaction fee configs
"""
DEFAULT_OP_RETURN_FEE = 10000
DEFAULT_DUST_FEE = 5500
DEFAULT_OP_RETURN_VALUE = 0
DEFAULT_FEE_PER_KB = 10000
""" magic bytes configs
"""
MAGIC_BYTES = 'id'
# borrowed from Blockstack
FIRST_BLOCK_MAINNET = 373601
if BLOCKSTACK_TEST and BLOCKSTACK_TEST_FIRST_BLOCK:
FIRST_BLOCK_MAINNET = int(BLOCKSTACK_TEST_FIRST_BLOCK)
print('TEST ACTIVE: FIRST_BLOCK_MAINNET = {}'.format(FIRST_BLOCK_MAINNET))
if (BLOCKSTACK_TESTNET or BLOCKSTACK_TESTNET3) and BLOCKSTACK_TESTNET_FIRST_BLOCK:
FIRST_BLOCK_MAINNET = int(BLOCKSTACK_TESTNET_FIRST_BLOCK)
print("TESTNET ACTIVE: FIRST_BLOCK_MAINNET = {}".format(FIRST_BLOCK_MAINNET))
FIRST_BLOCK_TIME_UTC = 1441737751
TX_MIN_CONFIRMATIONS = 6
if BLOCKSTACK_TEST:
# test environment
TX_MIN_CONFIRMATIONS = 0
print('TEST ACTIVE: TX_MIN_CONFIRMATIONS = {}'.format(TX_MIN_CONFIRMATIONS))
# borrowed from Blockstack
# Opcodes
ANNOUNCE = '#'
NAME_PREORDER = '?'
NAME_REGISTRATION = ':'
NAME_UPDATE = '+'
NAME_TRANSFER = '>'
NAME_RENEWAL = NAME_REGISTRATION
NAME_REVOKE = '~'
NAME_IMPORT = ';'
NAMESPACE_PREORDER = '*'
NAMESPACE_REVEAL = '&'
NAMESPACE_READY = '!'
# extra bytes affecting a transfer
TRANSFER_KEEP_DATA = '>'
TRANSFER_REMOVE_DATA = '~'
# borrowed from Blockstack Core
# these never change, so it's fine to duplicate them here
NAME_OPCODES = {
'NAME_PREORDER': NAME_PREORDER,
'NAME_REGISTRATION': NAME_REGISTRATION,
'NAME_UPDATE': NAME_UPDATE,
'NAME_TRANSFER': NAME_TRANSFER,
'NAME_RENEWAL': NAME_REGISTRATION,
'NAME_IMPORT': NAME_IMPORT,
'NAME_REVOKE': NAME_REVOKE,
'NAMESPACE_PREORDER': NAMESPACE_PREORDER,
'NAMESPACE_REVEAL': NAMESPACE_REVEAL,
'NAMESPACE_READY': NAMESPACE_READY,
'ANNOUNCE': ANNOUNCE
}
# borrowed from Blockstack Core
# these never change, so it's fine to duplicate them here
OPCODE_NAMES = {
NAME_PREORDER: 'NAME_PREORDER',
NAME_REGISTRATION: 'NAME_REGISTRATION',
NAME_UPDATE: 'NAME_UPDATE',
NAME_TRANSFER: 'NAME_TRANSFER',
NAME_RENEWAL: 'NAME_REGISTRATION',
NAME_REVOKE: 'NAME_REVOKE',
NAME_IMPORT: 'NAME_IMPORT',
NAMESPACE_PREORDER: 'NAMESPACE_PREORDER',
NAMESPACE_REVEAL: 'NAMESPACE_REVEAL',
NAMESPACE_READY: 'NAMESPACE_READY',
ANNOUNCE: 'ANNOUNCE'
}
# borrowed from Blockstack Core; needed by SNV
# these never change, so it's fine to duplicate them here
NAMEREC_FIELDS = [
'name', # the name itself
'value_hash', # the hash of the name's associated profile
'sender', # the scriptPubKey hex that owns this name (identifies ownership)
'sender_pubkey', # (OPTIONAL) the public key
'address', # the address of the sender
'block_number', # the block number when this name record was created (preordered for the first time)
'preorder_block_number', # the block number when this name was last preordered
'first_registered', # the block number when this name was registered by the current owner
'last_renewed', # the block number when this name was renewed by the current owner
'revoked', # whether or not the name is revoked
'op', # byte sequence describing the last operation to affect this name
'txid', # the ID of the last transaction to affect this name
'vtxindex', # the index in the block of the transaction.
'op_fee', # the value of the last Blockstack-specific burn fee paid for this name (i.e. from preorder or renew)
'importer', # (OPTIONAL) if this name was imported, this is the importer's scriptPubKey hex
'importer_address', # (OPTIONAL) if this name was imported, this is the importer's address
]
# borrowed from Blockstack Core; needed by SNV
# these never change, so it's fine to duplicate them here
NAMESPACE_FIELDS = [
'namespace_id', # human-readable namespace ID
'namespace_id_hash', # hash(namespace_id,sender,reveal_addr) from the preorder (binds this namespace to its preorder)
'version', # namespace rules version
'sender', # the scriptPubKey hex script that identifies the preorderer
'sender_pubkey', # if sender is a p2pkh script, this is the public key
'address', # address of the sender, from the scriptPubKey
'recipient', # the scriptPubKey hex script that identifies the revealer.
'recipient_address', # the address of the revealer
'block_number', # block number at which this namespace was preordered
'reveal_block', # block number at which this namespace was revealed
'op', # byte code identifying this operation to Blockstack
'txid', # transaction ID at which this namespace was revealed
'vtxindex', # the index in the block where the tx occurs
'lifetime', # how long names last in this namespace (in number of blocks)
'coeff', # constant multiplicative coefficient on a name's price
'base', # exponential base of a name's price
'buckets', # array that maps name length to the exponent to which to raise 'base' to
'nonalpha_discount', # multiplicative coefficient that drops a name's price if it has non-alpha characters
'no_vowel_discount', # multiplicative coefficient that drops a name's price if it has no vowels
]
# borrowed from Blockstack Core; needed by SNV
# these never change, so it's fine to duplicate them here
OPFIELDS = {
NAME_IMPORT: NAMEREC_FIELDS + [
'recipient', # scriptPubKey hex that identifies the name recipient
'recipient_address' # address of the recipient
],
NAMESPACE_PREORDER: [
'namespace_id_hash', # hash(namespace_id,sender,reveal_addr)
'consensus_hash', # consensus hash at the time issued
'op', # bytecode describing the operation (not necessarily 1 byte)
'op_fee', # fee paid for the namespace to the burn address
'txid', # transaction ID
'vtxindex', # the index in the block where the tx occurs
'block_number', # block number at which this transaction occurred
'sender', # scriptPubKey hex from the principal that issued this preorder (identifies the preorderer)
'sender_pubkey', # if sender is a p2pkh script, this is the public key
'address', # address from the scriptPubKey
],
NAMESPACE_REVEAL: NAMESPACE_FIELDS,
NAMESPACE_READY: NAMESPACE_FIELDS + [
'ready_block', # block number at which the namespace was readied
],
NAME_PREORDER: [
'preorder_hash', # hash(name,sender,register_addr)
'consensus_hash', # consensus hash at time of send
'sender', # scriptPubKey hex that identifies the principal that issued the preorder
'sender_pubkey', # if sender is a pubkeyhash script, then this is the public key. Otherwise, this is empty.
'address', # address from the sender's scriptPubKey
'block_number', # block number at which this name was preordered for the first time
'op', # blockstack bytestring describing the operation
'txid', # transaction ID
'vtxindex', # the index in the block where the tx occurs
'op_fee', # blockstack fee (sent to burn address)
],
NAME_REGISTRATION: NAMEREC_FIELDS + [
'recipient', # scriptPubKey hex script that identifies the principal to own this name
'recipient_address' # principal's address from the scriptPubKey in the transaction
],
NAME_REVOKE: NAMEREC_FIELDS,
NAME_TRANSFER: NAMEREC_FIELDS + [
'name_hash128', # hash(name)
'consensus_hash', # consensus hash when this operation was sent
'keep_data' # whether or not to keep the profile data associated with the name when transferred
],
NAME_UPDATE: NAMEREC_FIELDS + [
'name_consensus_hash', # hash(name,consensus_hash)
'consensus_hash' # consensus hash when this update was sent
]
}
# a few contants borrowed from Blockstack Core
LENGTH_VALUE_HASH = 20
LENGTH_CONSENSUS_HASH = 16
LENGTH_MAX_NAME = 37 # maximum name length
LENGTH_MAX_NAMESPACE_ID = 19 # maximum namespace length
# namespace version
BLOCKSTACK_VERSION = 1
NAME_SCHEME = MAGIC_BYTES + NAME_REGISTRATION
# burn address for fees (the address of public key
# 0x0000000000000000000000000000000000000000)
BLOCKSTACK_BURN_PUBKEY_HASH = '0000000000000000000000000000000000000000'
BLOCKSTACK_BURN_ADDRESS = virtualchain.hex_hash160_to_address(BLOCKSTACK_BURN_PUBKEY_HASH) # '1111111111111111111114oLvT2'
# borrowed from Blockstack Core
# never changes, so safe to duplicate to avoid gratuitous imports
MAXIMUM_NAMES_PER_ADDRESS = 25
MAX_RPC_LEN = 1024 * 1024 * 1024
RPC_MAX_ZONEFILE_LEN = 4096 # 4KB
RPC_MAX_PROFILE_LEN = 1024000 # 1MB
CONFIG_FILENAME = 'client.ini'
WALLET_FILENAME = 'wallet.json'
CONFIG_PATH = os.environ.get('BLOCKSTACK_CLIENT_CONFIG')
if not BLOCKSTACK_TEST:
# production
if CONFIG_PATH is None:
# default
CONFIG_DIR = os.path.expanduser("~/.blockstack")
CONFIG_PATH = os.path.join(CONFIG_DIR, CONFIG_FILENAME)
else:
# env value
CONFIG_DIR = os.path.dirname(CONFIG_PATH)
else:
# testing
assert CONFIG_PATH, 'BLOCKSTACK_CLIENT_CONFIG not set'
CONFIG_DIR = os.path.dirname(CONFIG_PATH)
print('TEST ACTIVE: CONFIG_PATH = {}'.format(CONFIG_PATH))
WALLET_PATH = os.path.join(CONFIG_DIR, 'wallet.json')
DEFAULT_QUEUE_PATH = os.path.join(CONFIG_DIR, 'queues.db')
METADATA_DIRNAME = 'metadata'
BLOCKCHAIN_ID_MAGIC = 'id'
USER_ZONEFILE_TTL = 3600 # cache lifetime for a user's zonefile
SLEEP_INTERVAL = 20 # in seconds
TX_EXPIRED_INTERVAL = 10 # if a tx is not picked up by x blocks
PREORDER_CONFIRMATIONS = 6
PREORDER_MAX_CONFIRMATIONS = 130 # no. of blocks after which preorder should be removed
TX_CONFIRMATIONS_NEEDED = 10
MAX_TX_CONFIRMATIONS = 130
QUEUE_LENGTH_TO_MONITOR = 50
MINIMUM_BALANCE = 0.002
DEFAULT_POLL_INTERVAL = 300
# approximate transaction sizes, for when the user has no balance.
# over-estimations, to avoid stalled registrations.
APPROX_PREORDER_TX_LEN = 620
APPROX_REGISTER_TX_LEN = 620
APPROX_UPDATE_TX_LEN = 1240
APPROX_TRANSFER_TX_LEN = 1240
APPROX_RENEWAL_TX_LEN = 1240
APPROX_REVOKE_TX_LEN = 1240
APPROX_NAMESPACE_PREORDER_TX_LEN = 620
APPROX_NAMESPACE_REVEAL_TX_LEN = 620
APPROX_NAMESPACE_READY_TX_LEN = 1240 # assumes three p2pkh inputs (more than required)
APPROX_NAMESPACE_IMPORT_TX_LEN = 1240 # assumes three p2pkh inputs (more than required)
# for estimating tx lengths, when we can't generate a transaction.
APPROX_TX_OVERHEAD_LEN = 12
APPROX_TX_IN_P2PKH_LEN = 150
APPROX_TX_OUT_P2PKH_LEN = 40
APPROX_TX_IN_P2SH_LEN = 300
APPROX_TX_OUT_P2SH_LEN = 40
TX_MAX_FEE = int(5 * 1e5)
# hardened children indexes
ACCOUNT_SIGNING_KEY_INDEX = 0
DATASTORE_SIGNING_KEY_INDEX = 0
# version of the storage protocol
BLOCKSTACK_STORAGE_PROTO_VERSION = 1
# session lifetime
DEFAULT_SESSION_LIFETIME = 3600 * 24 * 7 # 1 week
# epoch dates
EPOCH_1_END_BLOCK = 436650
# epoch dates for the test environment
NUM_EPOCHS = 2
for i in range(1, NUM_EPOCHS + 1):
# epoch lengths can be altered by the test framework, for ease of tests
blockstack_epoch_end_block = os.environ.get('BLOCKSTACK_EPOCH_{}_END_BLOCK'.format(i), None)
if blockstack_epoch_end_block is not None and BLOCKSTACK_TEST is not None:
exec('EPOCH_{}_END_BLOCK = int({})'.format(i, blockstack_epoch_end_block))
if DEBUG:
print('Envar: EPOCH_{}_END_BLOCK = {}'.format(i, eval('EPOCH_{}_END_BLOCK'.format(i))))
del i
EPOCH_HEIGHT_MINIMUM = EPOCH_1_END_BLOCK + 1
DEFAULT_BLOCKCHAIN_READER = 'blockstack_utxo'
DEFAULT_BLOCKCHAIN_WRITER = 'blockstack_utxo'
SECRETS = {}
def set_secret(key, value):
global SECRETS
SECRETS[key] = value
def get_secret(key):
return SECRETS.get(key)
def serialize_secrets():
return json.dumps(SECRETS)
def parse_secrets(buf):
try:
return json.loads(buf)
except:
return {}
def load_secrets(buf, is_file = False):
global SECRETS
if is_file:
try:
# aaron: this will read() from the file object until a json object
# is fully parsed, so it might stall waiting for inputs
sec = json.load(buf)
except:
sec = {}
else:
sec = parse_secrets(buf)
SECRETS.update(sec)
def write_secrets(buf):
"""
Given serialized secrets, save them to an
unlinked temporary file that the calling process
can read and write.
Be careful how we do this---we don't want another
process running as the same user
to be able to open the file in read mode.
Returns the (integer) file descriptor number on success.
Raises on error.
"""
dirp = tempfile.mkdtemp()
tmppath = os.path.join(dirp, "secrets")
fd = os.open(tmppath, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0200)
os.unlink(tmppath)
os.rmdir(dirp)
# NOTE: FD_CLOEXEC to stop the subprocess below from inheriting
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
flags |= fcntl.FD_CLOEXEC
fcntl.fcntl(fd, fcntl.F_SETFD, flags)
# try to verify that we got this file to ourselves
command = 'lsof -nP +L1 | grep "{}"'.format(tmppath)
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
out, err = p.communicate()
assert p.returncode == 0, "'{}' failed with code {}".format(command, p.returncode)
linecount = len(out.strip().split("\n"))
assert linecount == 1, "Some other process opened our secrets\n\n{}".format(out)
# save to write
os.write(fd, buf)
os.lseek(fd, 0, os.SEEK_SET)
# make accessible again
os.fchmod(fd, 0600)
# remove O_CLOEXEC (since we're passing this on exec to the reloaded process)
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
flags &= ~fcntl.FD_CLOEXEC
fcntl.fcntl(fd, fcntl.F_SETFD, flags)
return fd