Changeset View
Changeset View
Standalone View
Standalone View
test/functional/setup_scripts/chronik-client_plugins.py
Show All 16 Lines | from test_framework.address import ( | ||||
ADDRESS_ECREG_UNSPENDABLE, | ADDRESS_ECREG_UNSPENDABLE, | ||||
P2SH_OP_TRUE, | P2SH_OP_TRUE, | ||||
SCRIPTSIG_OP_TRUE, | SCRIPTSIG_OP_TRUE, | ||||
) | ) | ||||
from test_framework.blocktools import COINBASE_MATURITY | from test_framework.blocktools import COINBASE_MATURITY | ||||
from test_framework.messages import COutPoint, CTransaction, CTxIn, CTxOut | from test_framework.messages import COutPoint, CTransaction, CTxIn, CTxOut | ||||
from test_framework.script import OP_RETURN, CScript | from test_framework.script import OP_RETURN, CScript | ||||
from test_framework.txtools import pad_tx | from test_framework.txtools import pad_tx | ||||
from test_framework.util import assert_equal | from test_framework.util import assert_equal, chronik_sub_plugin | ||||
class ChronikClientPlugins(SetupFramework): | class ChronikClientPlugins(SetupFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
self.num_nodes = 1 | self.num_nodes = 1 | ||||
self.extra_args = [["-chronik"]] | self.extra_args = [["-chronik"]] | ||||
def skip_test_if_missing_module(self): | def skip_test_if_missing_module(self): | ||||
self.skip_if_no_chronik_plugins() | self.skip_if_no_chronik_plugins() | ||||
def run_test(self): | def run_test(self): | ||||
from test_framework.chronik.client import pb | from test_framework.chronik.client import pb | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
yield True | yield True | ||||
chronik = node.get_chronik_client() | chronik = node.get_chronik_client() | ||||
def ws_msg(txid: str, msg_type): | |||||
return pb.WsMsg( | |||||
tx=pb.MsgTx( | |||||
msg_type=msg_type, | |||||
txid=bytes.fromhex(txid)[::-1], | |||||
) | |||||
) | |||||
def assert_start_raises(*args, **kwargs): | def assert_start_raises(*args, **kwargs): | ||||
node.assert_start_raises_init_error(["-chronik"], *args, **kwargs) | node.assert_start_raises_init_error(["-chronik"], *args, **kwargs) | ||||
# Without a plugins.toml, setting up a plugin context is skipped | # Without a plugins.toml, setting up a plugin context is skipped | ||||
plugins_toml = os.path.join(node.datadir, "plugins.toml") | plugins_toml = os.path.join(node.datadir, "plugins.toml") | ||||
plugins_dir = os.path.join(node.datadir, "plugins") | plugins_dir = os.path.join(node.datadir, "plugins") | ||||
# Plugin that colors outputs with the corresponding PUSHDATA of the OP_RETURN, | # Plugin that colors outputs with the corresponding PUSHDATA of the OP_RETURN, | ||||
▲ Show 20 Lines • Show All 42 Lines • ▼ Show 20 Lines | """, | ||||
with node.assert_debug_log( | with node.assert_debug_log( | ||||
[ | [ | ||||
"Plugin context initialized Python", | "Plugin context initialized Python", | ||||
'Loaded plugin my_plugin.MyPluginPlugin (version 0.1.0) with LOKAD IDs [b"TEST"]', | 'Loaded plugin my_plugin.MyPluginPlugin (version 0.1.0) with LOKAD IDs [b"TEST"]', | ||||
] | ] | ||||
): | ): | ||||
self.restart_node(0, ["-chronik", "-chronikreindex"]) | self.restart_node(0, ["-chronik", "-chronikreindex"]) | ||||
# Init and websockets here so we can confirm msgs are sent server-side | |||||
ws1 = chronik.ws() | |||||
ws2 = chronik.ws() | |||||
coinblockhash = self.generatetoaddress(node, 1, ADDRESS_ECREG_P2SH_OP_TRUE)[0] | coinblockhash = self.generatetoaddress(node, 1, ADDRESS_ECREG_P2SH_OP_TRUE)[0] | ||||
coinblock = node.getblock(coinblockhash) | coinblock = node.getblock(coinblockhash) | ||||
cointx = coinblock["tx"][0] | cointx = coinblock["tx"][0] | ||||
self.log.info("Step 1: Empty regtest chain") | self.log.info("Step 1: Empty regtest chain") | ||||
yield True | yield True | ||||
self.log.info("Step 2: Send a tx to create plugin utxos in group 'a'") | self.log.info("Step 2: Send a tx to create plugin utxos in group 'a'") | ||||
self.generatetoaddress(node, COINBASE_MATURITY, ADDRESS_ECREG_UNSPENDABLE) | self.generatetoaddress(node, COINBASE_MATURITY, ADDRESS_ECREG_UNSPENDABLE) | ||||
# Subscribe to websockets in test script to support timing match in chronik-client integration tests | |||||
chronik_sub_plugin(ws1, node, "my_plugin", b"a") | |||||
chronik_sub_plugin(ws2, node, "my_plugin", b"b") | |||||
coinvalue = 5000000000 | coinvalue = 5000000000 | ||||
tx1 = CTransaction() | tx1 = CTransaction() | ||||
tx1.vin = [CTxIn(COutPoint(int(cointx, 16), 0), SCRIPTSIG_OP_TRUE)] | tx1.vin = [CTxIn(COutPoint(int(cointx, 16), 0), SCRIPTSIG_OP_TRUE)] | ||||
tx1.vout = [ | tx1.vout = [ | ||||
CTxOut(0, CScript([OP_RETURN, b"TEST", b"argo", b"alef", b"abc"])), | CTxOut(0, CScript([OP_RETURN, b"TEST", b"argo", b"alef", b"abc"])), | ||||
CTxOut(1000, P2SH_OP_TRUE), | CTxOut(1000, P2SH_OP_TRUE), | ||||
CTxOut(1000, P2SH_OP_TRUE), | CTxOut(1000, P2SH_OP_TRUE), | ||||
CTxOut(coinvalue - 10000, P2SH_OP_TRUE), | CTxOut(coinvalue - 10000, P2SH_OP_TRUE), | ||||
] | ] | ||||
pad_tx(tx1) | pad_tx(tx1) | ||||
node.sendrawtransaction(tx1.serialize().hex()) | node.sendrawtransaction(tx1.serialize().hex()) | ||||
assert_equal(ws1.recv(), ws_msg(tx1.hash, pb.TX_ADDED_TO_MEMPOOL)) | |||||
# Plugin ran on the mempool tx | # Plugin ran on the mempool tx | ||||
# Note: we must perform these assertions here before yield True | # Note: we must perform these assertions here before yield True | ||||
# Ensures that plugins are properly indexed before we query for them | # Ensures that plugins are properly indexed before we query for them | ||||
proto_tx1 = chronik.tx(tx1.hash).ok() | proto_tx1 = chronik.tx(tx1.hash).ok() | ||||
tx1_plugin_outputs = [ | tx1_plugin_outputs = [ | ||||
{}, | {}, | ||||
{"my_plugin": pb.PluginEntry(data=[b"argo"], groups=[b"a"])}, | {"my_plugin": pb.PluginEntry(data=[b"argo"], groups=[b"a"])}, | ||||
{"my_plugin": pb.PluginEntry(data=[b"alef"], groups=[b"a"])}, | {"my_plugin": pb.PluginEntry(data=[b"alef"], groups=[b"a"])}, | ||||
▲ Show 20 Lines • Show All 46 Lines • ▼ Show 20 Lines | """, | ||||
[tx1_plugin_outputs[1], tx1_plugin_outputs[2]], # "abc" spent | [tx1_plugin_outputs[1], tx1_plugin_outputs[2]], # "abc" spent | ||||
) | ) | ||||
proto_utxos2 = chronik.plugin("my_plugin").utxos(b"b").ok().utxos | proto_utxos2 = chronik.plugin("my_plugin").utxos(b"b").ok().utxos | ||||
assert_equal( | assert_equal( | ||||
[utxo.plugins for utxo in proto_utxos2], | [utxo.plugins for utxo in proto_utxos2], | ||||
tx2_plugin_outputs[1:], | tx2_plugin_outputs[1:], | ||||
) | ) | ||||
assert_equal(ws1.recv(), ws_msg(tx2.hash, pb.TX_ADDED_TO_MEMPOOL)) | |||||
assert_equal(ws2.recv(), ws_msg(tx2.hash, pb.TX_ADDED_TO_MEMPOOL)) | |||||
yield True | yield True | ||||
self.log.info("Step 4: Mine these first two transactions") | self.log.info("Step 4: Mine these first two transactions") | ||||
# Mine tx1 and tx2 | # Mine tx1 and tx2 | ||||
block1 = self.generatetoaddress(node, 1, ADDRESS_ECREG_UNSPENDABLE)[-1] | block1 = self.generatetoaddress(node, 1, ADDRESS_ECREG_UNSPENDABLE)[-1] | ||||
# Lexicographic order | |||||
txids = sorted([tx1.hash, tx2.hash]) | |||||
assert_equal(ws1.recv(), ws_msg(txids[0], pb.TX_CONFIRMED)) | |||||
assert_equal(ws1.recv(), ws_msg(txids[1], pb.TX_CONFIRMED)) | |||||
assert_equal(ws2.recv(), ws_msg(tx2.hash, pb.TX_CONFIRMED)) | |||||
yield True | yield True | ||||
self.log.info("Step 5: Send a third tx to create plugin utxos in group 'c'") | self.log.info("Step 5: Send a third tx to create plugin utxos in group 'c'") | ||||
tx3 = CTransaction() | tx3 = CTransaction() | ||||
tx3.vin = [ | tx3.vin = [ | ||||
CTxIn(COutPoint(tx2.sha256, 1), SCRIPTSIG_OP_TRUE), | CTxIn(COutPoint(tx2.sha256, 1), SCRIPTSIG_OP_TRUE), | ||||
CTxIn(COutPoint(tx2.sha256, 3), SCRIPTSIG_OP_TRUE), | CTxIn(COutPoint(tx2.sha256, 3), SCRIPTSIG_OP_TRUE), | ||||
] | ] | ||||
tx3.vout = [ | tx3.vout = [ | ||||
CTxOut(0, CScript([OP_RETURN, b"TEST", b"carp"])), | CTxOut(0, CScript([OP_RETURN, b"TEST", b"carp"])), | ||||
CTxOut(coinvalue - 30000, P2SH_OP_TRUE), | CTxOut(coinvalue - 30000, P2SH_OP_TRUE), | ||||
] | ] | ||||
pad_tx(tx3) | pad_tx(tx3) | ||||
node.sendrawtransaction(tx3.serialize().hex()) | node.sendrawtransaction(tx3.serialize().hex()) | ||||
assert_equal(ws2.recv(), ws_msg(tx3.hash, pb.TX_ADDED_TO_MEMPOOL)) | |||||
proto_tx3 = chronik.tx(tx3.hash).ok() | proto_tx3 = chronik.tx(tx3.hash).ok() | ||||
tx3_plugin_inputs = [tx2_plugin_outputs[1], tx2_plugin_outputs[3]] | tx3_plugin_inputs = [tx2_plugin_outputs[1], tx2_plugin_outputs[3]] | ||||
tx3_plugin_outputs = [ | tx3_plugin_outputs = [ | ||||
{}, | {}, | ||||
{ | { | ||||
"my_plugin": pb.PluginEntry( | "my_plugin": pb.PluginEntry( | ||||
data=[b"carp", b"blub", b"abc"], groups=[b"c"] | data=[b"carp", b"blub", b"abc"], groups=[b"c"] | ||||
), | ), | ||||
Show All 19 Lines | """, | ||||
) | ) | ||||
yield True | yield True | ||||
self.log.info("Step 6: Mine this tx") | self.log.info("Step 6: Mine this tx") | ||||
# Mine tx3 | # Mine tx3 | ||||
block2 = self.generatetoaddress(node, 1, ADDRESS_ECREG_UNSPENDABLE)[-1] | block2 = self.generatetoaddress(node, 1, ADDRESS_ECREG_UNSPENDABLE)[-1] | ||||
assert_equal(ws2.recv(), ws_msg(tx3.hash, pb.TX_CONFIRMED)) | |||||
yield True | yield True | ||||
self.log.info("Step 7: Invalidate the block with the third tx") | self.log.info("Step 7: Invalidate the block with the third tx") | ||||
# Disconnect block2, inputs + outputs still work | # Disconnect block2, inputs + outputs still work | ||||
node.invalidateblock(block2) | node.invalidateblock(block2) | ||||
assert_equal(ws2.recv(), ws_msg(tx3.hash, pb.TX_ADDED_TO_MEMPOOL)) | |||||
yield True | yield True | ||||
self.log.info("Step 8: Invalidate the block with the first two txs") | self.log.info("Step 8: Invalidate the block with the first two txs") | ||||
node.invalidateblock(block1) | node.invalidateblock(block1) | ||||
# Topological order | |||||
assert_equal(ws1.recv(), ws_msg(tx1.hash, pb.TX_ADDED_TO_MEMPOOL)) | |||||
assert_equal(ws1.recv(), ws_msg(tx2.hash, pb.TX_ADDED_TO_MEMPOOL)) | |||||
# Reorg first clears the mempool and then adds back in topological order | |||||
assert_equal(ws2.recv(), ws_msg(tx3.hash, pb.TX_REMOVED_FROM_MEMPOOL)) | |||||
assert_equal(ws2.recv(), ws_msg(tx2.hash, pb.TX_ADDED_TO_MEMPOOL)) | |||||
assert_equal(ws2.recv(), ws_msg(tx3.hash, pb.TX_ADDED_TO_MEMPOOL)) | |||||
yield True | yield True | ||||
if __name__ == "__main__": | if __name__ == "__main__": | ||||
ChronikClientPlugins().main() | ChronikClientPlugins().main() |