Skip to content

Instantly share code, notes, and snippets.

@diemesleno
Last active September 26, 2022 10:58
Show Gist options
  • Save diemesleno/03ec85986ca62c4c600f8b0d9d8ad4bf to your computer and use it in GitHub Desktop.
Save diemesleno/03ec85986ca62c4c600f8b0d9d8ad4bf to your computer and use it in GitHub Desktop.
Gist para o desafio de Pré-Seleção seminário da Viteum

Solução para o desafio de pré-seleção da Vinteum

1. Compilação do Bitcoin Core (Ubuntu 22.04 LTS)

a) Instalando as dependências:

sudo apt install build-essential libtool autotools-dev automake pkg-config bsdmainutils python3 libevent-dev libboost-dev libsqlite3-dev libminiupnpc-dev libnatpmp-dev libzmq3-dev systemtap-sdt-dev libqrencode-dev python3-zmq shellcheck -y

b) Clonando o projeto:

git clone https://github.com/bitcoin/bitcoin.git

c) Verificar a última release e efetuar o checkou para a última versão:

cd bitcoin

git tag | sort -V

git checkout v23.0

d) Verificar a assinatura da release:

Se necessário, importar a chave do mantenedor do projeto:

gpg --keyserver keyserver.ubuntu.com --recv 9DEAE0DC7063249FB05474681E4AED62986CD25D

Validar a assinatura:

git verify-tag v23.0

e) Mantendo a compatibilidade com legacy wallets

Definindo o local para o Berkeley DB 4.8

export BDB_PREFIX='/dados/bitcoin/db4'

Instalando o banco legado

./contrib/install_db4.sh `pwd`

f) Preparando a instalação

./autogen.sh

g) Configurando

./configure BDB_LIBS="-L${BDB_PREFIX}/lib -ldb_cxx-4.8" BDB_CFLAGS="-I${BDB_PREFIX}/include"

h) Compilando

make -j $(nproc)

i) Instalando

sudo make install

2. Executando os testes unitários e os funcionais:

a) Criando um ambiente virtual para Python

OBS: Com o virtualenv e virtualenvwrapper instalados e configurados...

mkvirtualenv devbitcoin 

b) Atualizando pip e instalando dependências

pip install --upgrade pip setuptools wheel

pip install flake8 mypy pyzmq vulture codespell

c) Executando todos os testes

test/functional/test_runner.py --extended

Teremos como saída algo como:

Temporary test directory at /tmp/test_runner_₿_🏃_20220917_171328
Running Unit Tests for Test Framework Modules
..........
----------------------------------------------------------------------
Ran 10 tests in 0.574s

OK
1/240 - wallet_hd.py --legacy-wallet skipped
2/240 - wallet_backup.py --legacy-wallet skipped
3/240 - wallet_hd.py --descriptors passed, Duration: 4 s
...
238/240 - feature_config_args.py passed, Duration: 14 s
239/240 - feature_blockfilterindex_prune.py passed, Duration: 6 s
240/240 - feature_dbcrash.py passed, Duration: 1866 s

Após execução dos testes, será apresentado o relatório informando os testes que passaram e os que por ventura falharam, conforme:

TEST                                               | STATUS    | DURATION

example_test.py                                    | ✓ Passed  | 1 s
feature_abortnode.py                               | ✓ Passed  | 31 s
feature_addrman.py                                 | ✓ Passed  | 3 s
...
wallet_txn_clone.py --segwit                       | ✓ Passed  | 1 s
wallet_txn_doublespend.py --descriptors            | ✓ Passed  | 1 s
wallet_txn_doublespend.py --mineblock              | ✓ Passed  | 3 s
feature_backwards_compatibility.py --descriptors   | ○ Skipped | 1 s
feature_backwards_compatibility.py --legacy-wallet | ○ Skipped | 0 s
feature_bind_port_discover.py                      | ○ Skipped | 0 s
...
wallet_watchonly.py --legacy-wallet                | ○ Skipped | 0 s
wallet_watchonly.py --usecli --legacy-wallet       | ○ Skipped | 0 s
mining_prioritisetransaction.py                    | ✖ Failed  | 0 s

ALL                                                | ✖ Failed  | 3554 s (accumulated) 
Runtime: 1866 s

Se as dependências do projeto forem satisfeitas, os testes unitários serão automaticamente compilados junto com o projeto.

Desta forma, também podemos executá-los, no diretório do projeto, conforme:

make check

Teremos algo como:

...
PASS: minisketch/test
PASS: univalue/test/object
PASS: univalue/test/unitester
PASS: univalue/test/no_nul
===========================================================================
Testsuite summary for Bitcoin Core 23.0.0
===========================================================================
# TOTAL: 4
# PASS:  4
# SKIP:  0
# XFAIL: 0
# FAIL:  0
# XPASS: 0
# ERROR: 0
===========================================================================
...
PASS: tests
PASS: exhaustive_tests
===========================================================================
Testsuite summary for libsecp256k1 0.1
===========================================================================
# TOTAL: 2
# PASS:  2
# SKIP:  0
# XFAIL: 0
# FAIL:  0
# XPASS: 0
# ERROR: 0
===========================================================================


3. Analisar o arquivo example_test.py no diretório dos testes funcionais e tentar fazer com que o node 1 minere um novo bloco, envie para o node 2 e verifique se o node 2 recebeu o bloco.

a) Fiz uma cópia do arquivo example_test.py como p2p_vinteum_test.py

b) Alterações realizadas no arquivo:

Pelo que entendi deste arquivo, ele já fazia praticamente tudo o que pedia no desafio.

A única diferença é que estava sendo gerado 10 novos blocos e o desafio pediu para minerar 1 bloco.

Então, praticamente só tirei o loop for para que não gerasse/minerasse 10 blocos mas sim apenas 1 novo bloco.

Adicionei também mais alguns logs.

De resto, o novo bloco está sendo propagado para o segundo e o segundo confirma o recebimento.

c) Executei o teste funcional individual criado conforme:

test/functional/p2p_vinteum_test.py

Como resultado tive:

2022-09-17T17:52:43.788000Z TestFramework (INFO): Initializing test directory /tmp/bitcoin_func_test_dnkyqshq
2022-09-17T17:52:44.195000Z TestFramework (INFO): Starting mining vinteum test!
2022-09-17T17:52:44.322000Z TestFramework (INFO): Actual block height: 1
2022-09-17T17:52:44.322000Z TestFramework (INFO): Creating a new block
2022-09-17T17:52:44.322000Z TestFramework (INFO): Mining the new block
2022-09-17T17:52:44.323000Z TestFramework (INFO): Wait for node1 to reach current tip (height 2) using RPC
2022-09-17T17:52:44.328000Z TestFramework (INFO): Connect node2 and node1
2022-09-17T17:52:44.393000Z TestFramework (INFO): Wait for node2 to receive all the blocks from node1
2022-09-17T17:52:44.407000Z TestFramework (INFO): Add P2P connection to node2
2022-09-17T17:52:44.514000Z TestFramework (INFO): Test that node2 propagates all the blocks to us
2022-09-17T17:52:44.565000Z TestFramework (INFO): Check that each block was received only once
2022-09-17T17:52:44.615000Z TestFramework (INFO): Stopping nodes
2022-09-17T17:52:44.770000Z TestFramework (INFO): Cleaning up /tmp/bitcoin_func_test_dnkyqshq on exit
2022-09-17T17:52:44.770000Z TestFramework (INFO): Tests successful

That's it.

#!/usr/bin/env python3
# Copyright (c) 2017-2021 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""An example functional test
The module-level docstring should include a high-level description of
what the test is doing. It's the first thing people see when they open
the file and should give the reader information about *what* the test
is testing and *how* it's being tested
"""
# Imports should be in PEP8 ordering (std library first, then third party
# libraries then local imports).
from collections import defaultdict
# Avoid wildcard * imports
from test_framework.blocktools import (create_block, create_coinbase)
from test_framework.messages import CInv, MSG_BLOCK
from test_framework.p2p import (
P2PInterface,
msg_block,
msg_getdata,
p2p_lock,
)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
)
# P2PInterface is a class containing callbacks to be executed when a P2P
# message is received from the node-under-test. Subclass P2PInterface and
# override the on_*() methods if you need custom behaviour.
class BaseNode(P2PInterface):
def __init__(self):
"""Initialize the P2PInterface
Used to initialize custom properties for the Node that aren't
included by default in the base class. Be aware that the P2PInterface
base class already stores a counter for each P2P message type and the
last received message of each type, which should be sufficient for the
needs of most tests.
Call super().__init__() first for standard initialization and then
initialize custom properties."""
super().__init__()
# Stores a dictionary of all blocks received
self.block_receive_map = defaultdict(int)
def on_block(self, message):
"""Override the standard on_block callback
Store the hash of a received block in the dictionary."""
message.block.calc_sha256()
self.block_receive_map[message.block.sha256] += 1
def on_inv(self, message):
"""Override the standard on_inv callback"""
pass
class ExampleTest(BitcoinTestFramework):
# Each functional test is a subclass of the BitcoinTestFramework class.
# Override the set_test_params(), skip_test_if_missing_module(), add_options(), setup_chain(), setup_network()
# and setup_nodes() methods to customize the test setup as required.
def set_test_params(self):
"""Override test parameters for your individual test.
This method must be overridden and num_nodes must be explicitly set."""
# By default every test loads a pre-mined chain of 200 blocks from cache.
# Set setup_clean_chain to True to skip this and start from the Genesis
# block.
self.setup_clean_chain = True
self.num_nodes = 3
# Use self.extra_args to change command-line arguments for the nodes
self.extra_args = [[], ["-logips"], []]
# self.log.info("I've finished set_test_params") # Oops! Can't run self.log before run_test()
# Use skip_test_if_missing_module() to skip the test if your test requires certain modules to be present.
# This test uses generate which requires wallet to be compiled
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
# Use add_options() to add specific command-line options for your test.
# In practice this is not used very much, since the tests are mostly written
# to be run in automated environments without command-line options.
# def add_options()
# pass
# Use setup_chain() to customize the node data directories. In practice
# this is not used very much since the default behaviour is almost always
# fine
# def setup_chain():
# pass
def setup_network(self):
"""Setup the test network topology
Often you won't need to override this, since the standard network topology
(linear: node0 <-> node1 <-> node2 <-> ...) is fine for most tests.
If you do override this method, remember to start the nodes, assign
them to self.nodes, connect them and then sync."""
self.setup_nodes()
# In this test, we're not connecting node2 to node0 or node1. Calls to
# sync_all() should not include node2, since we're not expecting it to
# sync.
self.connect_nodes(0, 1)
self.sync_all(self.nodes[0:2])
# Use setup_nodes() to customize the node start behaviour (for example if
# you don't want to start all nodes at the start of the test).
# def setup_nodes():
# pass
def run_test(self):
"""Main test logic"""
self.log.info("Starting mining vinteum test!")
# Create P2P connections will wait for a verack to make sure the connection is fully up
peer_messaging = self.nodes[0].add_p2p_connection(BaseNode())
# Generating a block on one of the nodes will get us out of IBD
blocks = [int(self.generate(self.nodes[0], sync_fun=lambda: self.sync_all(self.nodes[0:2]), nblocks=1)[0], 16)]
# Notice above how we called an RPC by calling a method with the same
# name on the node object. Notice also how we used a keyword argument
# to specify a named RPC argument. Neither of those are defined on the
# node object. Instead there's some __getattr__() magic going on under
# the covers to dispatch unrecognised attribute calls to the RPC
# interface.
# Logs are nice. Do plenty of them. They can be used in place of comments for
# breaking the test into sub-sections.
self.tip = int(self.nodes[0].getbestblockhash(), 16)
self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1
height = self.nodes[0].getblockcount()
self.log.info(f"Actual block height: {height}")
self.log.info("Creating a new block")
block = create_block(self.tip, create_coinbase(height+1), self.block_time)
self.log.info("Mining the new block")
block.solve()
block_message = msg_block(block)
# Send message is used to send a P2P message to the node over our P2PInterface
peer_messaging.send_message(block_message)
self.tip = block.sha256
blocks.append(self.tip)
self.block_time += 1
height += 1
self.log.info("Wait for node1 to reach current tip (height 2) using RPC")
self.nodes[1].waitforblockheight(2)
self.log.info("Connect node2 and node1")
self.connect_nodes(1, 2)
self.log.info("Wait for node2 to receive all the blocks from node1")
self.sync_all()
self.log.info("Add P2P connection to node2")
self.nodes[0].disconnect_p2ps()
peer_receiving = self.nodes[2].add_p2p_connection(BaseNode())
self.log.info("Test that node2 propagates all the blocks to us")
getdata_request = msg_getdata()
for block in blocks:
getdata_request.inv.append(CInv(MSG_BLOCK, block))
peer_receiving.send_message(getdata_request)
# wait_until() will loop until a predicate condition is met. Use it to test properties of the
# P2PInterface objects.
peer_receiving.wait_until(lambda: sorted(blocks) == sorted(list(peer_receiving.block_receive_map.keys())), timeout=5)
self.log.info("Check that each block was received only once")
# The network thread uses a global lock on data access to the P2PConnection objects when sending and receiving
# messages. The test thread should acquire the global lock before accessing any P2PConnection data to avoid locking
# and synchronization issues. Note p2p.wait_until() acquires this global lock internally when testing the predicate.
with p2p_lock:
for block in peer_receiving.block_receive_map.values():
assert_equal(block, 1)
if __name__ == '__main__':
ExampleTest().main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment