mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-14 12:43:40 +02:00
assets_tutorial: basically rewrite the whole thing
This commit is contained in:
parent
6a74528a40
commit
1d2dacb1e2
4 changed files with 509 additions and 252 deletions
|
|
@ -56,7 +56,10 @@ class Daemon():
|
|||
|
||||
def shutdown(self):
|
||||
if self.proc is not None:
|
||||
self.proc.kill()
|
||||
print ("Shutting down %s" % self.name)
|
||||
self.proc.terminate()
|
||||
## FIXME determine why we need 30+ seconds to shut down with a tiny regtest chain
|
||||
self.proc.wait(120)
|
||||
self.proc = None
|
||||
|
||||
if self.datadir_path is not None:
|
||||
|
|
@ -65,21 +68,34 @@ class Daemon():
|
|||
else:
|
||||
print ("Leaving %s datadir at %s." % (self.name, self.datadir_path))
|
||||
|
||||
def start(self, ext_args = None):
|
||||
self.shutdown()
|
||||
def start(self, ext_args = None, keep_datadir = False):
|
||||
if keep_datadir and self.datadir_path is not None:
|
||||
temp = self.datadir_path
|
||||
self.datadir_path = None
|
||||
self.shutdown()
|
||||
self.datadir_path = temp
|
||||
else:
|
||||
self.shutdown()
|
||||
# Create datadir and copy config into place
|
||||
self.datadir_path = tempfile.mkdtemp()
|
||||
shutil.copyfile(self.conf_path, self.datadir_path + '/' + self.daemon + '.conf')
|
||||
print("%s datadir: %s" % (self.name, self.datadir_path))
|
||||
|
||||
# Start process
|
||||
print ("Starting %s" % self.name)
|
||||
if ext_args is None:
|
||||
ext_args = []
|
||||
# Create datadir and copy config into place
|
||||
self.datadir_path = tempfile.mkdtemp()
|
||||
shutil.copyfile(self.conf_path, self.datadir_path + '/' + self.daemon + '.conf')
|
||||
print("%s datadir: %s" % (self.name, self.datadir_path))
|
||||
# Start process
|
||||
self.proc = subprocess.Popen([self.path, "-datadir=" + self.datadir_path] + ext_args, stdout=subprocess.PIPE)
|
||||
self.proc = subprocess.Popen([self.path, "-datadir=" + self.datadir_path] + ext_args)
|
||||
self.rpc = AuthServiceProxy("http://" + self.config["rpcuser"] + ":" + self.config["rpcpassword"] + "@127.0.0.1:" + self.config["rpcport"])
|
||||
|
||||
def restart(self, ext_args = None):
|
||||
self.start(ext_args)
|
||||
# Give daemon a moment to start up
|
||||
time.sleep(1)
|
||||
|
||||
def connect_to(self, other):
|
||||
self.addnode("localhost:%s" % other.config['port'], "onetry")
|
||||
|
||||
def restart(self, ext_args = None, keep_datadir = False):
|
||||
self.start(ext_args, keep_datadir)
|
||||
|
||||
def __del__(self):
|
||||
self.shutdown()
|
||||
|
|
@ -95,11 +111,17 @@ class Daemon():
|
|||
def sync_all(nodes, timeout_sec = 10):
|
||||
totalWait = timeout_sec
|
||||
|
||||
stop_time = time.time() + timeout
|
||||
stop_time = time.time() + timeout_sec
|
||||
while time.time() <= stop_time:
|
||||
best_hash = [x.getbestblockhash() for x in nodes]
|
||||
if best_hash.count(best_hash[0]) == len(nodes):
|
||||
break
|
||||
time.sleep(1)
|
||||
while time.time() <= stop_time:
|
||||
pool = [set(x.getrawmempool()) for x in nodes]
|
||||
if pool.count(pool[0]) == len(nodes):
|
||||
return
|
||||
time.sleep(1)
|
||||
raise Exception("Nodes cannot sync blocks or mempool!")
|
||||
|
||||
# Setup daemons
|
||||
|
|
@ -128,6 +150,7 @@ e2 = Daemon(
|
|||
)
|
||||
|
||||
## 1. Start nodes
|
||||
print ("1. Start nodes")
|
||||
#
|
||||
# 1a. Confirm that we not start an elements node if validatepegin is set and there
|
||||
# is no bitcoind. When validatepegin is set, elementsd attempts to connect to
|
||||
|
|
@ -136,11 +159,11 @@ e2 = Daemon(
|
|||
# Alternatively, you can set validatepegin=0 (it defaults to being on) in the
|
||||
# elementsd config, and run it without a Bitcoin node, but this means that you
|
||||
# will not be fully validating the two-way peg.
|
||||
print ("1a. Attempting to start a validatepegin daemon without a bitcoind (will fail)")
|
||||
|
||||
assert e1["validatepegin"] == "1"
|
||||
|
||||
e1.start()
|
||||
time.sleep(1) ## give daemon a moment to start up (or not)
|
||||
try:
|
||||
e1.getinfo()
|
||||
print ("ERROR: was able to start an elementsd without a working bitcoind")
|
||||
|
|
@ -151,106 +174,208 @@ except:
|
|||
# 1b. Start bitcoind, then elementsd. Initially, the bitcoind may be warming up and
|
||||
# inaccessible over RPC. elementsd can detect this case and will stall until the
|
||||
# bitcoind is warmed up.
|
||||
print ("1b. Attempting to start validatepegin daemons with a bitcoind (will succeed)")
|
||||
|
||||
bitcoin.start()
|
||||
e1.start()
|
||||
e2.start()
|
||||
|
||||
time.sleep(1) ## give daemons a moment to start up
|
||||
# Connect the nodes. This can also be accomplished with the `connect=` config
|
||||
# parameter, but when starting two nodes simultaneously, this is unreliable.
|
||||
e1.connect_to(e2)
|
||||
e2.connect_to(e1)
|
||||
|
||||
# 1c. Create a wallet on the Elements nodes. This is needed since version 0.21
|
||||
# of the daemon; previously a wallet was created by default if one does not
|
||||
# already exist.
|
||||
e1.createwallet("wallet1")
|
||||
e2.createwallet("wallet2")
|
||||
print ("1c. Creating wallets on all daemons")
|
||||
|
||||
# We have configured this regtest chain to start with 21M bitcoins, which are initally
|
||||
# in a single OP_TRUE output. All Elements wallets recognize OP_TRUE outputs as their
|
||||
# own (this differs from Bitcoin), so the 21M bitcoins are immediately available for
|
||||
# use.
|
||||
# use. This can be disabled by setting `anyonecanspend_aremine=0` in the daemon config.
|
||||
#
|
||||
# This is useful for testing basic functionality and for blockchains that have no peg,
|
||||
# since every blockchain needs a default "policy asset". This policy asset is used
|
||||
# for transaction fees (which are required for anti-DoS purposes). Also, asset
|
||||
# issuances require some pre-existing asset, since they consume inputs for entropy.
|
||||
#
|
||||
# To separate the policy asset (used for fees) from the peg asset, use the `-policyasset`
|
||||
# configuration value.
|
||||
#
|
||||
# In Elements there is no block subsidy. In a production sidechain, `initialfreecoins`
|
||||
# will likely be set to zero, necessitating peg-in functionality to get a policy asset.
|
||||
|
||||
e1.createwallet("wallet1")
|
||||
e2.createwallet("wallet2")
|
||||
|
||||
# Because of https://github.com/ElementsProject/elements/issues/956 we need to run
|
||||
# `rescanblockchain` after creating the wallets to detect the `TRUE` outputs
|
||||
assert e1["initialfreecoins"] == "2100000000000000"
|
||||
print (e1.getwalletinfo())
|
||||
assert e1.getwalletinfo()['balance'] == { 'bitcoin': 0 }
|
||||
e1.rescanblockchain()
|
||||
e2.rescanblockchain()
|
||||
assert e1.getwalletinfo()['balance'] == { 'bitcoin': 21000000 }
|
||||
assert e2.getwalletinfo()['balance'] == { 'bitcoin': 21000000 }
|
||||
# All the initial coins coins are in one UTXO
|
||||
assert len(e1.listunspent()) == 1
|
||||
# ...and both nodes think they own it. This is a common situation when
|
||||
# using near-empty test chains, so be aware of it.
|
||||
assert e1.listunspent() == e2.listunspent()
|
||||
|
||||
print ("Waiting 2 mins")
|
||||
time.sleep(120) ## give daemons a moment to start up
|
||||
sys.exit(0)
|
||||
# In regtest mining "target" is OP_TRUE since we have not set `-signblockscript` argument
|
||||
# Generate simply works.
|
||||
e1.generatetoaddress(101, e1.getnewaddress())
|
||||
sync_all(e1, e2)
|
||||
# Generate 10 blocks to demonstrate how block generation work. On our test chain,
|
||||
# each block has a subsidy of zero (this can be changed with `con_blocksubsidy`)
|
||||
# and sends the coins to an OP_TRUE output (this script can be changed with
|
||||
# the `-signblockscript` config).
|
||||
e1.generatetoaddress(10, e1.getnewaddress())
|
||||
# The wallet does not recognize zero-valued outputs as being owned
|
||||
assert len(e1.listunspent()) == 1
|
||||
assert e1.getwalletinfo()['balance'] == { 'bitcoin': 21000000 }
|
||||
|
||||
# WALLET
|
||||
# Synchronize the chains
|
||||
sync_all([e1, e2])
|
||||
assert e1.getblockcount() == 10
|
||||
assert e1.getbestblockhash() == e2.getbestblockhash()
|
||||
|
||||
# First, send all anyone-can-spend coins to e1 then split so balances are even
|
||||
e1.sendtoaddress(e1.getnewaddress(), 21000000, "", "", True)
|
||||
e1.generatetoaddress(101, e1.getnewaddress())
|
||||
sync_all(e1, e2)
|
||||
e1.sendtoaddress(e2.getnewaddress(), 10500000, "", "", False)
|
||||
e1.generatetoaddress(101, e1.getnewaddress())
|
||||
sync_all(e1, e2)
|
||||
## 2. Basic wallet usage
|
||||
print ("")
|
||||
print ("2. Basic wallet usage")
|
||||
|
||||
# Funds should now be evenly split between the two wallets
|
||||
e1.getwalletinfo()
|
||||
e2.getwalletinfo()
|
||||
# 2a. Send all the coins to e1
|
||||
# Observe that this address is a confidential address (is much longer
|
||||
# than an ordinary address). Using the `validateaddress` RPC, you can
|
||||
# see details of the address, including its unconfidential version.
|
||||
addr = e1.getnewaddress()
|
||||
print ("2a. Sending all initial coins to e1 (address %s)", addr)
|
||||
txid1 = e1.sendtoaddress(addr, 21000000, "", "", True)
|
||||
e1.generatetoaddress(1, e1.getnewaddress())
|
||||
sync_all([e1, e2])
|
||||
assert len(e1.listunspent()) == 1 # change output, but no coinbase
|
||||
assert len(e2.listunspent()) == 0
|
||||
|
||||
# Have e2 send coins to themself using a blinded Elements address
|
||||
# Blinded addresses start with `CTE`, unblinded `2`
|
||||
# 2b. Send half of these to e2
|
||||
addr = e2.getnewaddress()
|
||||
print ("2b. Sending half to e2 (address %s)." % addr)
|
||||
txid2 = e1.sendtoaddress(addr, 10500000, "", "", False)
|
||||
e1.generatetoaddress(1, e1.getnewaddress())
|
||||
assert len(e1.listunspent()) == 1 # change output, but no coinbase
|
||||
e1.generatetoaddress(99, e1.getnewaddress())
|
||||
assert len(e1.listunspent()) == 2 # change output, and coinbase with fees from first transaction
|
||||
e1.generatetoaddress(1, e1.getnewaddress())
|
||||
assert len(e1.listunspent()) == 3 # ...and fees from the second transaction
|
||||
sync_all([e1, e2])
|
||||
assert len(e2.listunspent()) == 1
|
||||
|
||||
# How do we know it's blinded? Check for blinding key, unblinded address.
|
||||
e2.getaddressinfo(addr)
|
||||
# Funds should now be evenly split between the two wallets. e2 directly
|
||||
# received 10500000, while e1 has the remainder, less fees (because it
|
||||
# created the transactions), plus fees (because it created the blocks).
|
||||
assert e1.getbalance() == e2.getbalance()
|
||||
assert e1.listunspent() != e2.listunspent()
|
||||
|
||||
# Basic blinded send
|
||||
txid = e2.sendtoaddress(addr, 1)
|
||||
# 2c. Self-send this half to e2 again
|
||||
addr = e2.getnewaddress()
|
||||
print ("2c. Self-sending this half to e2 (address %s)." % addr)
|
||||
txid3 = e2.sendtoaddress(addr, 10500000, "", "", True)
|
||||
sync_all([e1, e2])
|
||||
e1.generatetoaddress(101, e1.getnewaddress())
|
||||
sync_all([e1, e2])
|
||||
|
||||
e2.generatetoaddress(1, e1.getnewaddress())
|
||||
sync_all(e1, e2)
|
||||
# New e1 has slightly more coins than e2, because it received the fees
|
||||
# from the last transaction
|
||||
assert len(e1.listunspent()) == 4
|
||||
assert len(e2.listunspent()) == 1
|
||||
assert e1.getbalance()['bitcoin'] > e2.getbalance()['bitcoin']
|
||||
|
||||
# Now let's examine the transaction, both in wallet and without
|
||||
# 2d. Analyze transactions
|
||||
print ("2d. Analyzing transactions.")
|
||||
|
||||
# In-wallet, take a look at blinding information
|
||||
e2.gettransaction(txid)
|
||||
# The first transaction is a 1-input-2-output transaction starting from an
|
||||
# unblinded input and where one output (the fee) must be unblinded. Since
|
||||
# blinding the remaining output would accomplish nothing, it is unblinded
|
||||
# even though a confidential address was used.
|
||||
tx1 = e1.getrawtransaction(txid1, True)
|
||||
assert len(tx1['vin']) == 1
|
||||
assert len(tx1['vout']) == 2
|
||||
assert all(['value' in out for out in tx1['vout']])
|
||||
# The second transaction though has three outputs, including change. Now
|
||||
# there is value in blinding, so both the destination output and change
|
||||
# output are blinded
|
||||
tx2 = e1.getrawtransaction(txid2, True)
|
||||
assert len(tx2['vin']) == 1
|
||||
assert len(tx2['vout']) == 3
|
||||
assert any(['value-minimum' in out for out in tx2['vout']])
|
||||
# The third transaction, which set subtractfeefromamount (the `True` passed
|
||||
# to `sendtoaddress`), will again be a 1-input-2-output transaction, where
|
||||
# one output is the unblinded fee. But since its input is confidential, the
|
||||
# output will be too.
|
||||
tx3 = e1.getrawtransaction(txid3, True)
|
||||
assert len(tx3['vin']) == 1
|
||||
assert len(tx3['vout']) == 2
|
||||
assert any(['value' in out for out in tx3['vout']])
|
||||
assert any(['value-minimum' in out for out in tx3['vout']])
|
||||
|
||||
# e1 doesn't have in wallet since it's unrelated
|
||||
# Check that these transactions are visible in the correct wallets, with the
|
||||
# expected effects. Check that they are not visible in the opposing wallet
|
||||
assert e1.gettransaction(txid1)['amount'] == { 'bitcoin': 0 }
|
||||
assert e2.gettransaction(txid1)['amount']['bitcoin'] < -20999999 # exact value depends on fee
|
||||
assert e1.gettransaction(txid2)['amount'] == { 'bitcoin': -10500000 }
|
||||
assert e2.gettransaction(txid2)['amount'] == { 'bitcoin': 10500000 }
|
||||
assert e2.gettransaction(txid3)['amount'] == { 'bitcoin': 0 }
|
||||
|
||||
# txid3 appears only in e2, not e1
|
||||
try:
|
||||
e1.gettransaction(txid)
|
||||
raise Exception("Transaction should not be in wallet")
|
||||
e1.gettransaction(txid3)
|
||||
except JSONRPCException:
|
||||
pass
|
||||
else:
|
||||
raise Exception("Transaction 3 should not be in wallet 1")
|
||||
|
||||
# Get public info, see blinded ranges, etc
|
||||
e1.getrawtransaction(txid, 1)
|
||||
## 3. Confidential assets and keys
|
||||
print ("")
|
||||
print ("3. Confidential Keys")
|
||||
current_e1_balance = e1.getbalance()
|
||||
|
||||
# 3a. Import an address's secret key
|
||||
print ("3a. Import an address's secret key")
|
||||
# Recall that `addr` was last set to an address owned by e2, and which
|
||||
# we sent 10.5 million coins to in `tx3`. The public data (mostly hidden)
|
||||
# is visible with `getrawtransaction`, while the confidential data is
|
||||
# visible on `e2` (but not `e1`") with `gettransaction`.
|
||||
|
||||
# Now let's private import the key to attempt a spend
|
||||
e1.importprivkey(e2.dumpprivkey(addr))
|
||||
# Now `gettransaction` no longer triggers an exception, but the confidential
|
||||
# data is still not available.
|
||||
assert e1.gettransaction(txid3)['details'] == []
|
||||
assert e2.gettransaction(txid3)['details'] != []
|
||||
# Its output won't appear in listunspent, and the wallet balance will be unaffected
|
||||
assert len(e1.listunspent()) == 4
|
||||
assert e1.getbalance() == current_e1_balance
|
||||
|
||||
# We can't see output value info though
|
||||
# and can not send.
|
||||
e1.gettransaction(txid)
|
||||
|
||||
# And it won't show in balance or known outputs
|
||||
e1.getwalletinfo()
|
||||
# Amount for transaction is unknown, so it is not shown in listunspent.
|
||||
e1.listunspent(1, 1)
|
||||
|
||||
# 3a. Import an address's blinding key
|
||||
print ("3b. Import an address's blinding key")
|
||||
# Solution: Import blinding key
|
||||
e1.importblindingkey(addr, e2.dumpblindingkey(addr))
|
||||
|
||||
# Check again, funds should show
|
||||
e1.getwalletinfo()
|
||||
e1.listunspent(1, 1)
|
||||
e1.gettransaction(txid)
|
||||
assert len(e1.listunspent()) == 5
|
||||
assert e1.getbalance()['bitcoin'] > current_e1_balance['bitcoin']
|
||||
assert e1.gettransaction(txid3)['details'] != []
|
||||
|
||||
# Move funds to fresh addresses to avoid any confusion related to shared
|
||||
# coins down the line (e.g. conflicting transactions).
|
||||
e1.sendtoaddress(e1.getnewaddress(), e1.getbalance()['bitcoin'], "", "", True)
|
||||
e1.sendtoaddress(e2.getnewaddress(), e1.getbalance()['bitcoin'] / 2, "", "", True)
|
||||
e1.generatetoaddress(1, e1.getnewaddress())
|
||||
sync_all([e1, e2])
|
||||
|
||||
## 4. 2-of-2 multisig
|
||||
#
|
||||
# Let's build a blinded 2-of-2 multisig p2sh address
|
||||
print ("")
|
||||
print ("4. 2-of-2 multisig")
|
||||
|
||||
print ("4a. Create a multisig address.")
|
||||
# 1) Get unblinded addresses from each participant
|
||||
addr1 = e1.getaddressinfo(e1.getnewaddress())["unconfidential"]
|
||||
addr2 = e2.getaddressinfo(e2.getnewaddress())["unconfidential"]
|
||||
|
|
@ -264,6 +389,7 @@ blindingpubkey = addrinfo1["confidential_key"]
|
|||
# 3) Make multisig address like usual
|
||||
multisig = e1.createmultisig(2, [addrinfo1["pubkey"], addrinfo2["pubkey"]])
|
||||
|
||||
print ("4b. Blind the multisig address (using a blinding key from e1).")
|
||||
# 4) Blind the address using the blinding pubkey
|
||||
blinded_addr = e1.createblindedaddress(multisig["address"], blindingpubkey)
|
||||
e1.importaddress(multisig["redeemScript"], "", True, True) # Make sure p2sh addr is added
|
||||
|
|
@ -273,105 +399,171 @@ e2.importaddress(blinded_addr)
|
|||
|
||||
# 5) Now the address can be funded, though e2 will not be able to see values
|
||||
txid = e1.sendtoaddress(blinded_addr, 1)
|
||||
sync_all(e1, e2)
|
||||
e2.gettransaction(txid, True)
|
||||
sync_all([e1, e2])
|
||||
assert e1.gettransaction(txid, True)['details'] != []
|
||||
assert e2.gettransaction(txid, True)['details'] == []
|
||||
|
||||
# 6) Import the blinding privkey and decode the values
|
||||
print ("4c. Share the blinding key with e2")
|
||||
e2.importblindingkey(blinded_addr, blindingkey)
|
||||
e2.gettransaction(txid, True)
|
||||
assert e1.gettransaction(txid, True)['details'] != []
|
||||
assert e2.gettransaction(txid, True)['details'] != []
|
||||
|
||||
# ASSETS
|
||||
|
||||
# Many of the RPC calls have added asset type or label
|
||||
# arguments and reveal alternative asset information. With no argument all are listed:
|
||||
e1.getwalletinfo()
|
||||
|
||||
# Notice we now see "bitcoin" as an asset. This is the asset label for the hex for "bitcoin" which can be discovered:
|
||||
e1.dumpassetlabels()
|
||||
## 5. Multi-asset support
|
||||
#
|
||||
# Many of the RPC calls have added asset type or label arguments, and reveal
|
||||
# alternative asset information. With no argument all are listed. For example,
|
||||
# try `getwalletinfo` or `getbalance`. (Notice in the above code our assertions
|
||||
# have taken forms like {"bitcoin": 100} rather than bare numbers.)
|
||||
#
|
||||
# Notice we now see "bitcoin" as an asset. This is the asset label for the hex
|
||||
# for "bitcoin" which can be discovered using the `dumpassetlabels` RPC. We
|
||||
# can see more details of each issuance that your wallet knows about with the
|
||||
# `listissuances` RPC. Initially there is only one asset, "bitcoin", and one
|
||||
# issuance (the initial issuance).
|
||||
print ("")
|
||||
print ("5. Multi-asset support")
|
||||
print ("Existing assets: ", e1.dumpassetlabels())
|
||||
assert len(e1.dumpassetlabels()) == 1
|
||||
assert len(e1.listissuances()) == 1
|
||||
assert e1.listissuances()[0]['assetlabel'] == "bitcoin"
|
||||
assert e1.listissuances()[0]['assetamount'] == 21000000 # 21M initial free coins
|
||||
assert e1.listissuances()[0]['tokenamount'] == 0 # no reissuance tokens
|
||||
assert e1.dumpassetlabels() == e2.dumpassetlabels()
|
||||
|
||||
print ("5a. Issue a new asset, with reissuance token")
|
||||
# We can also issue our own assets, 1 asset and 1 reissuance token in this case
|
||||
issue = e1.issueasset(1, 1)
|
||||
asset = issue["asset"]
|
||||
|
||||
# From there you can look at the issuances you have in your wallet
|
||||
e1.listissuances()
|
||||
assert len(e1.listissuances()) == 2
|
||||
assert len(e2.listissuances()) == 1 ## e2 does not recognize this as a wallet issuance
|
||||
new_issuances = [i for i in e1.listissuances() if 'assetlabel' not in i]
|
||||
assert len(new_issuances) == 1
|
||||
assert new_issuances[0]['assetamount'] == 1
|
||||
assert new_issuances[0]['tokenamount'] == 1
|
||||
|
||||
assert len(e2.listissuances()) == 1 ## ANDREW
|
||||
print ("5b. Reissue the asset using the reissuance token.")
|
||||
# If you gave `issueasset` a reissuance token argument greater than 0
|
||||
# you can also reissue the base asset
|
||||
# you can also reissue the base asset. This will appear as a second issuance
|
||||
# in `listissuances`.
|
||||
e1.reissueasset(asset, 1)
|
||||
new_issuances = [i for i in e1.listissuances() if 'assetlabel' not in i]
|
||||
assert len(new_issuances) == 2
|
||||
assert new_issuances[0]['assetamount'] == 1
|
||||
assert new_issuances[1]['assetamount'] == 1
|
||||
# The original issuance will show `tokenamount` while the new one will not have
|
||||
# this field. Python makes it annoying to assert this.
|
||||
|
||||
# or make another different unblinded asset issuance, with only reissuance tokens initially
|
||||
e1.issueasset(0, 1, False)
|
||||
print ("5c. Issue a new asset with only reissuance tokens, no actual asset.")
|
||||
new_issue = e1.issueasset(0, 1, False) # `False` tells elementsd not to blind the issuance
|
||||
assert len(e1.listissuances()) == 4
|
||||
assert len(e2.listissuances()) == 1
|
||||
issuance = [i for i in e1.listissuances() if i.get('asset') == new_issue['asset']][0]
|
||||
assert issuance['assetamount'] == -1 # should be 0, see https://github.com/ElementsProject/elements/issues/1035
|
||||
assert issuance['tokenamount'] == 1
|
||||
|
||||
# Then two issuances for that particular asset will show
|
||||
e1.listissuances(asset)
|
||||
sync_all([e1, e2])
|
||||
e1.generatetoaddress(1, e1.getnewaddress())
|
||||
sync_all([e1, e2])
|
||||
|
||||
print ("5d. Label a new asset.")
|
||||
# To label any asset add a new argument like this to your elements.conf file
|
||||
# then restart your daemon:
|
||||
assetentry = "-assetdir="+asset+":namedasset"
|
||||
# then restart your daemon. Remember to reload the wallet after restarting.
|
||||
# Wallet labels have no consensus meaning, only local node/wallet meaning
|
||||
assetentry = "-assetdir="+asset+":namedasset"
|
||||
e1.restart([assetentry], keep_datadir=True)
|
||||
|
||||
sync_all(e1, e2)
|
||||
e1.stop()
|
||||
time.sleep(5)
|
||||
assert e1.getbestblockhash() == e2.getbestblockhash() ## sanity check that node remembers the blockchain
|
||||
e1.connect_to(e2)
|
||||
e2.connect_to(e1)
|
||||
e1.loadwallet("wallet1")
|
||||
|
||||
# Restart with a new asset label
|
||||
e1 = startelementsd(e1_datadir, e1conf, [assetentry])
|
||||
time.sleep(5)
|
||||
|
||||
e1.getwalletinfo()
|
||||
# The new label will be reflected in the RPC
|
||||
assert e1.getwalletinfo()['balance']['namedasset'] == 2
|
||||
assert e1.getbalance()['namedasset'] == 2
|
||||
|
||||
print ("5e. Transfer assets.")
|
||||
# To send issued assets, add an additional argument to sendtoaddress using the hex or label
|
||||
e1.sendtoaddress(address=e2.getnewaddress(), amount=1, assetlabel="namedasset")
|
||||
# Reissuance tokens can also be sent like any other asset
|
||||
e1.sendtoaddress(address=e2.getnewaddress(), amount=1, assetlabel=issue["token"])
|
||||
sync_all(e1, e2)
|
||||
sync_all([e1, e2])
|
||||
# e2 wallet doesn't know about label, just an unnamed asset
|
||||
e2.getwalletinfo()["unconfirmed_balance"][asset]
|
||||
assert e2.getwalletinfo()["unconfirmed_balance"][asset] == 1
|
||||
assert "namedasset" not in e2.getwalletinfo()["unconfirmed_balance"]
|
||||
e2.generatetoaddress(1, e2.getnewaddress())
|
||||
sync_all(e1, e2)
|
||||
sync_all([e1, e2])
|
||||
|
||||
# e2 maybe doesn't know about the issuance for the transaction sending him the new asset
|
||||
e2.listissuances()
|
||||
# e2, despite receiving an asset, continues not to know about its issuances
|
||||
assert len(e2.listissuances()) == 1
|
||||
# ...and therefore, despite receiving a reissuance token, does not understand
|
||||
# it and cannot use it to issue
|
||||
try:
|
||||
e2.reissueasset(issue["asset"], 5)
|
||||
except JSONRPCException:
|
||||
pass
|
||||
else:
|
||||
raise Exception("Should not be able to reissue a reissuance token")
|
||||
|
||||
# let's import an associated address(so the wallet captures issuance transaction) and rescan
|
||||
print ("5e. Import an address used in an issuance.")
|
||||
# However, if we import the address used in the issuance transaction and
|
||||
# rescan, the wallet _will_ learn about the issuance, although it will
|
||||
# not know about the amounts (which are blinded)
|
||||
txid = issue["txid"]
|
||||
addr = e1.gettransaction(txid)["details"][0]["address"]
|
||||
e2.importaddress(addr)
|
||||
|
||||
# e2 now sees issuance, but doesn't know amounts as they are blinded
|
||||
e2.listissuances()
|
||||
assert len(e2.listissuances()) == 2
|
||||
new_issuances = [i for i in e2.listissuances() if 'assetlabel' not in i]
|
||||
assert len(new_issuances) == 1
|
||||
assert new_issuances[0]['assetamount'] == -1
|
||||
assert new_issuances[0]['tokenamount'] == -1
|
||||
|
||||
# At this point, e2 knows that its reissuance token is actually a reissuance
|
||||
# token, and can use it to reissue. Even though it does not know about the
|
||||
# original issuance.
|
||||
e2.reissueasset(issue["asset"], 1)
|
||||
|
||||
# We need to import the issuance blinding key. We refer to issuances by their txid/vin pair
|
||||
# as there is only one per input
|
||||
vin = issue["vin"]
|
||||
issuekey = e1.dumpissuanceblindingkey(txid, vin)
|
||||
|
||||
e2.importissuanceblindingkey(txid, vin, issuekey)
|
||||
|
||||
# Now e2 can see issuance amounts and blinds
|
||||
e2.listissuances()
|
||||
assert len(e2.listissuances()) == 3
|
||||
new_issuances = [i for i in e2.listissuances() if 'assetlabel' not in i]
|
||||
assert len(new_issuances) == 2
|
||||
assert new_issuances[0]['assetamount'] == 1
|
||||
assert new_issuances[1]['assetamount'] == 1
|
||||
|
||||
# Since it was also sent a reissuance token, it can reissue the base asset
|
||||
e2.reissueasset(issue["asset"], 5)
|
||||
|
||||
|
||||
# Reissuing reissuance tokens is currently not supported
|
||||
# Reissuing reissuance tokens is not supported
|
||||
try:
|
||||
e2.reissueasset(issue["token"], 1)
|
||||
except JSONRPCException:
|
||||
pass
|
||||
else:
|
||||
raise Exception("Should not be able to reissue a reissuance token")
|
||||
|
||||
# For de-issuance, we can send assets or issuance tokens to an OP_RETURN output, provably burning them
|
||||
e2.destroyamount(issue["asset"], 5)
|
||||
e2.destroyamount(issue["asset"], 1)
|
||||
|
||||
# BLOCKSIGNING
|
||||
|
||||
# Recall blocksigning is OP_TRUE
|
||||
sync_all([e1, e2])
|
||||
e1.generatetoaddress(1, e1.getnewaddress())
|
||||
sync_all(e1, e2)
|
||||
sync_all([e1, e2])
|
||||
|
||||
# Let's set it to something more interesting... 2-of-2 multisig
|
||||
## 6. Blocksigning
|
||||
#
|
||||
# Up to now, we have been generating blocks to the default OP_TRUE script. Let's
|
||||
# make this script more interesting. We'll use a 2-of-2 multisig made from keys
|
||||
# from our two Elements nodes.
|
||||
#
|
||||
|
||||
print ("")
|
||||
print ("6. Blocksigning")
|
||||
print ("6a. Generating 2-of-2 blocksigning script")
|
||||
|
||||
# First lets get some keys from both clients to make our block "challenge"
|
||||
addr1 = e1.getnewaddress()
|
||||
|
|
@ -384,46 +576,51 @@ pubkey2 = valid2["pubkey"]
|
|||
key1 = e1.dumpprivkey(addr1)
|
||||
key2 = e2.dumpprivkey(addr2)
|
||||
|
||||
e1.stop()
|
||||
e2.stop()
|
||||
time.sleep(5)
|
||||
|
||||
# Now filled with the pubkeys as 2-of-2 checkmultisig
|
||||
signblockarg="-signblockscript=5221"+pubkey1+"21"+pubkey2+"52ae"
|
||||
# Anti-DoS argument, custom chain default is ~1 sig so let's make it at least 2 sigs
|
||||
blocksign_max_size="-con_max_block_sig_size=150"
|
||||
dyna_deploy_start="-con_dyna_deploy_start=0"
|
||||
# We need to define a witness script, which defines the 2-of-2 checkmultisig
|
||||
witness_script = "5221" + pubkey1 + "21" + pubkey2 + "52ae"
|
||||
extra_args = [
|
||||
signblockarg,
|
||||
blocksign_max_size,
|
||||
dyna_deploy_start,
|
||||
# We set the signblockscript to the witness script
|
||||
"-signblockscript=" + witness_script,
|
||||
# To prevent malleability attacks, we must set a maximum signature size. Since
|
||||
# we expect to have two ECDSA signatures (each at most 73 bytes), 150 bytes is
|
||||
# a sufficient value.
|
||||
"-con_max_block_sig_size=150",
|
||||
# We also disable dynamic federations, since we are not going to do any
|
||||
# dynafed transitions in this tutorial. FIXME we probably should.
|
||||
"-con_dyna_deploy_start=0",
|
||||
]
|
||||
|
||||
# Wipe out datadirs, start over
|
||||
shutil.rmtree(e1_datadir)
|
||||
shutil.rmtree(e2_datadir)
|
||||
os.makedirs(e1_datadir)
|
||||
os.makedirs(e2_datadir)
|
||||
print ("6b. Restart both nodes")
|
||||
# Restart both nodes with the new consensus rules
|
||||
e1.restart(extra_args)
|
||||
e2.restart(extra_args)
|
||||
e1.connect_to(e2)
|
||||
e2.connect_to(e1)
|
||||
|
||||
# Copy back config files
|
||||
shutil.copyfile("contrib/assets_tutorial/elements1.conf", e1_datadir+"/elements.conf")
|
||||
shutil.copyfile("contrib/assets_tutorial/elements2.conf", e2_datadir+"/elements.conf")
|
||||
|
||||
e1 = startelementsd(e1_datadir, e1conf, extra_args)
|
||||
e2 = startelementsd(e2_datadir, e2conf, extra_args)
|
||||
time.sleep(5)
|
||||
sync_all(e1, e2)
|
||||
# We cleared the datadirs, but even if we had not, changing the consensus
|
||||
# rules would have invalidated the past blockchain and required we reset
|
||||
# anyway. Now we have only the genesis block.
|
||||
assert e1.getblockcount() == 0
|
||||
assert e2.getblockcount() == 0
|
||||
assert e1.getbestblockhash() == e2.getbestblockhash()
|
||||
|
||||
print ("6c. Import signing keys")
|
||||
# Now import signing keys
|
||||
e1.createwallet("wallet1")
|
||||
e2.createwallet("wallet2")
|
||||
e1.importprivkey(key1)
|
||||
e2.importprivkey(key2)
|
||||
|
||||
# Generate no longer works, even if keys are in wallet
|
||||
# Generate no longer works, since neither node has sufficiently
|
||||
# many keys to sign a block. In fact, even if both keys were
|
||||
# available, `generatetoaddress` would not work because it does
|
||||
# not attempt to solve the blocksigning script.
|
||||
try:
|
||||
e1.generatetoaddress(1, e1.getnewaddress())
|
||||
raise Exception("Generate shouldn't work")
|
||||
except JSONRPCException:
|
||||
pass
|
||||
else:
|
||||
raise Exception("Generate shouldn't work")
|
||||
|
||||
try:
|
||||
e1.generatetoaddress(1, e1.getnewaddress())
|
||||
|
|
@ -431,143 +628,192 @@ try:
|
|||
except JSONRPCException:
|
||||
pass
|
||||
|
||||
# Let's propose and accept some blocks, e1 is master!
|
||||
print ("6d. Propose a block")
|
||||
|
||||
# Have e1 propose a block for both nodes to sign
|
||||
blockhex = e1.getnewblockhex()
|
||||
|
||||
# Unsigned is no good
|
||||
# Without signing the block, it is not accepted by consensus
|
||||
# 0 before, 0 after
|
||||
e1.getblockcount() == 0
|
||||
|
||||
assert e1.getblockcount() == 0
|
||||
e1.submitblock(blockhex)
|
||||
assert e1.getblockcount() == 0
|
||||
|
||||
# Still 0
|
||||
e1.getblockcount() == 0
|
||||
|
||||
|
||||
print ("6d. Sign the block")
|
||||
# Signblock tests validity except block signatures
|
||||
# This signing step can be outsourced to a HSM signing to enforce business logic of any sort
|
||||
# See Strong Federations paper
|
||||
sign1 = e1.signblock(blockhex)
|
||||
sign2 = e2.signblock(blockhex)
|
||||
sign1 = e1.signblock(blockhex, witness_script)
|
||||
sign2 = e2.signblock(blockhex, witness_script)
|
||||
assert len(sign1) == 1 # both nodes produce one signature
|
||||
assert len(sign2) == 1
|
||||
|
||||
|
||||
# We now can gather signatures any way you want, combine them into a fully signed block
|
||||
# Obtain signatures from both nodes. Both signatures are required for the block
|
||||
# to be considered complete.
|
||||
blockresult = e1.combineblocksigs(blockhex, [sign1[0]])
|
||||
assert not blockresult["complete"]
|
||||
blockresult = e1.combineblocksigs(blockhex, [sign2[0]])
|
||||
assert not blockresult["complete"]
|
||||
blockresult = e1.combineblocksigs(blockhex, [sign1[0], sign2[0]])
|
||||
assert blockresult["complete"]
|
||||
|
||||
blockresult["complete"] == True
|
||||
|
||||
signedblock = blockresult["hex"]
|
||||
|
||||
# Now submit the block, doesn't matter who
|
||||
e2.submitblock(signedblock)
|
||||
sync_all(e1, e2)
|
||||
print ("6d. Submit the block")
|
||||
# Either node may submit the block to the network
|
||||
e2.submitblock(blockresult["hex"])
|
||||
sync_all([e1, e2])
|
||||
|
||||
# We now have moved forward one block!
|
||||
e1.getblockcount() == 1
|
||||
e2.getblockcount() == 1
|
||||
assert e1.getblockcount() == 1
|
||||
assert e2.getblockcount() == 1
|
||||
|
||||
e1.stop()
|
||||
e2.stop()
|
||||
time.sleep(5)
|
||||
## The peg
|
||||
#
|
||||
# Everything peg-related can be done inside the Elements daemon directly,
|
||||
# except for processing pegouts. This is because processing pegouts involves
|
||||
# moving coins on the Bitcoin blockchain. In a production system, this is
|
||||
# the most difficult part to get right, and by far the most important, as
|
||||
# there is no going back if you lose funds on Bitcoin.
|
||||
#
|
||||
print ("")
|
||||
print ("7. Dealing with the peg")
|
||||
print ("7a. Restart both nodes")
|
||||
|
||||
# Further Exercises:
|
||||
# - Make a python script that does round-robin consensus
|
||||
extra_args = [
|
||||
# We'll lazily reuse our blocksigning script to handle the peg, and
|
||||
# leave the blocksigning script unset (so it will revert to OP_TRUE)
|
||||
"-fedpegscript=" + witness_script,
|
||||
# Set initial free coins to 0, since we will now use the peg
|
||||
"-initialfreecoins=0",
|
||||
]
|
||||
# Restart both nodes with the new consensus rules
|
||||
e1.restart(extra_args)
|
||||
e2.restart(extra_args)
|
||||
e1.connect_to(e2)
|
||||
e2.connect_to(e1)
|
||||
|
||||
# Pegging
|
||||
e1.createwallet("wallet1")
|
||||
e2.createwallet("wallet2")
|
||||
e1.rescanblockchain()
|
||||
e2.rescanblockchain()
|
||||
assert e1.getwalletinfo()['balance'] == { 'bitcoin': 0 }
|
||||
assert e2.getwalletinfo()['balance'] == { 'bitcoin': 0 }
|
||||
|
||||
# Everything pegging related can be done inside the Elements daemon directly, except for
|
||||
# pegging out. This is due to the multisig pool aka Watchmen that controls the bitcoin
|
||||
# on the Bitcoin blockchain. That is the easiest part to get wrong, and by far the most
|
||||
# important as there is no going back if you lose the funds.
|
||||
# Generate some Elements blocks as we cannot accept pegin claims
|
||||
# while the blockheight is 0 (this may be unintended behavior).
|
||||
e1.generatetoaddress(5, e1.getnewaddress())
|
||||
sync_all([e1, e2])
|
||||
|
||||
# Wipe out datadirs, start over
|
||||
shutil.rmtree(e1_datadir)
|
||||
shutil.rmtree(e2_datadir)
|
||||
os.makedirs(e1_datadir)
|
||||
os.makedirs(e2_datadir)
|
||||
|
||||
# Copy back config files
|
||||
shutil.copyfile("contrib/assets_tutorial/elements1.conf", e1_datadir+"/elements.conf")
|
||||
shutil.copyfile("contrib/assets_tutorial/elements2.conf", e2_datadir+"/elements.conf")
|
||||
|
||||
fedpegarg="-fedpegscript=5221"+pubkey1+"21"+pubkey2+"52ae"
|
||||
|
||||
# Back to OP_TRUE blocks, re-using pubkeys for pegin pool instead
|
||||
# Keys can be the same or different, doesn't matter
|
||||
e1 = startelementsd(e1_datadir, e1conf, [fedpegarg])
|
||||
e2 = startelementsd(e2_datadir, e2conf, [fedpegarg])
|
||||
time.sleep(5)
|
||||
|
||||
# Mature some outputs on each side
|
||||
e1.generatetoaddress(101, e1.getnewaddress())
|
||||
# Create some mature Bitcoin outputs
|
||||
bitcoin.createwallet("bwallet")
|
||||
bitcoin.generatetoaddress(101, bitcoin.getnewaddress())
|
||||
sync_all(e1, e2)
|
||||
|
||||
# Now we can actually start pegging in. Examine the pegin address fields
|
||||
e1.getpeginaddress()
|
||||
# Changes each time as it's a new sidechain address as well as new "tweak" for the watchmen keys
|
||||
# mainchain_address : where you send your bitcoin from Bitcoin network
|
||||
# sidechain_address : where the bitcoin will end up on the sidechain after pegging in
|
||||
print ("7b. Create a peg-in address")
|
||||
# Once there are mature coins on the Bitcoin side, we can peg them in.
|
||||
# We create a pegin address, which we create with the Elements wallet
|
||||
# but which is a Bitcoin address.
|
||||
#
|
||||
# Internally, this address is computed by generating an Elements address
|
||||
# then using the underlying script, called a "claim script", to "tweak"
|
||||
# the peg witness script.
|
||||
#
|
||||
# The claim script is provided by the `getpeginaddress` RPC, but it is
|
||||
# not necessary to keep, for most usecases, because it is also stored
|
||||
# in the wallet.
|
||||
peg_in_addr = e1.getpeginaddress()
|
||||
assert "mainchain_address" in peg_in_addr
|
||||
assert "claim_script" in peg_in_addr
|
||||
|
||||
# Each call of this takes the pubkeys defined in the config file, adds a random number to them
|
||||
# that is essetially the hash of the sidechain_address and other information,
|
||||
# then creates a new P2SH Bitcoin address from that. We reveal that "tweak" to the functionaries
|
||||
# during `claimpegin`, then they are able to calculate the necessary private key and control
|
||||
# funds.
|
||||
addrs = e1.getpeginaddress()
|
||||
print ("7c. Send Bitcoin to the pegin address")
|
||||
txid = bitcoin.sendtoaddress(peg_in_addr["mainchain_address"], 10)
|
||||
|
||||
#Send funds to unique watchmen P2SH address
|
||||
txid = bitcoin.sendtoaddress(addrs["mainchain_address"], 1)
|
||||
print ("7d. Claim the Bitcoin on the sidechain")
|
||||
# Once the coins are sent on the Bitcoin side, they will not be recognized
|
||||
# or accepted by the sidechain until they are buried by 100 confirmations.
|
||||
# This value may be changed by use of the `-peginconfirmationdepth` config
|
||||
# setting on the Elements daemon.
|
||||
#
|
||||
# Bear in mind that this is a consensus rule and all nodes must agree on it.
|
||||
|
||||
# First, try claiming the pegin early. This will fail.
|
||||
bitcoin.generatetoaddress(1, bitcoin.getnewaddress())
|
||||
|
||||
# Confirmations in Bitcoin are what protects the
|
||||
# sidechain from becoming fractional reserve during reorgs.
|
||||
bitcoin.generatetoaddress(101, bitcoin.getnewaddress())
|
||||
proof = bitcoin.gettxoutproof([txid])
|
||||
raw = bitcoin.getrawtransaction(txid)
|
||||
try:
|
||||
claimtxid = e1.claimpegin(raw, proof, peg_in_addr["claim_script"])
|
||||
except JSONRPCException:
|
||||
pass
|
||||
else:
|
||||
raise Exception("Should not be able to claim a pegin early")
|
||||
|
||||
# Attempt claim!
|
||||
claimtxid = e1.claimpegin(raw, proof, addrs["claim_script"])
|
||||
sync_all(e1, e2)
|
||||
|
||||
# Other node should accept to mempool and mine
|
||||
# After 100 blocks, the claim will work. Note that the original proof will
|
||||
# work, as long as no reorgs changed which block the pegin transaction was
|
||||
# included in.
|
||||
bitcoin.generatetoaddress(100, bitcoin.getnewaddress())
|
||||
claimtxid = e1.claimpegin(raw, proof, peg_in_addr["claim_script"])
|
||||
# Mine this from the other node, to confirm that mempool propagation works
|
||||
sync_all([e1, e2])
|
||||
e2.generatetoaddress(1, e1.getnewaddress())
|
||||
sync_all(e1, e2)
|
||||
sync_all([e1, e2])
|
||||
|
||||
# Should see confirmations
|
||||
"confirmations" in e1.getrawtransaction(claimtxid, 1)
|
||||
assert "confirmations" in e1.getrawtransaction(claimtxid, 1)
|
||||
sys.exit(0)
|
||||
|
||||
# Pegging Out
|
||||
print ("7e. Request pegout")
|
||||
# This burns coins on Liquid using a specially-structured OP_RETURN output.
|
||||
# In a production network, doing this would trigger the watchman federation
|
||||
# to send payment to the specified Bitcoin address on the mainchain.
|
||||
#
|
||||
# The Bitcoin-side functionality is not supported directly in Elements;
|
||||
# the watchmen are expected to notice this transaction and send the funds
|
||||
# from their collective wallet.
|
||||
e1.sendtomainchain(bitcoin.getnewaddress(), 5)
|
||||
|
||||
# This command would trigger watchmen to send payment to Bitcoin address on mainchain
|
||||
# The Bitcoin-side functionality is not supported directly in Elements.
|
||||
# The watchmen will notice this transaction and send the funds from their collective
|
||||
# wallet.
|
||||
e1.sendtomainchain(bitcoin.getnewaddress(), 10)
|
||||
|
||||
#Exercise(s)
|
||||
#1. Implement really dumb/unsafe watchmen to allow pegouts for learning purposes
|
||||
# Recover tweak from pegin, add to privkey, combined tweaked pubkeys into a redeemscript, add to Core wallet
|
||||
## Exercise(s)
|
||||
#
|
||||
# 1. Implement really dumb/unsafe watchmen to allow pegouts for learning purposes.
|
||||
# To custody the coins that users peg in, you need to extract the tweak from
|
||||
# the pegin claim, use this tweak to adjust the secret keys, and import the
|
||||
# resulting keys to a Core wallet.
|
||||
#
|
||||
# 2. Create an alternate peg from testnet to Liquid, using asset issuance and
|
||||
# destruction.
|
||||
#
|
||||
# 3. Implement a round-robin blocksigner protocol, where each signer takes a turn
|
||||
# proposing blocks for the others to sign.
|
||||
#
|
||||
|
||||
print ("")
|
||||
print ("8. Raw transaction demo")
|
||||
# RAW API
|
||||
|
||||
# Let's create a basic transaction using the raw api, blind it, sign, and send
|
||||
|
||||
# Create a transaction with a single destination output to other wallet
|
||||
rawtx = e1.createrawtransaction([], {e2.getnewaddress():100})
|
||||
# Biggest difference compared to Bitcoin is that we have explicit fee outputs
|
||||
rawtx2 = e1.createrawtransaction([], {e2.getnewaddress():100, e1.getnewaddress():5, "fee":Decimal("0.1")})
|
||||
# Fee outputs are unblinded, with a scriptPubKey of "", in other words ""
|
||||
# scriptPubKeys are unspendable
|
||||
# Create a transaction with a single destination output to other wallet.
|
||||
rawtx = e1.createrawtransaction([], [{ e2.getnewaddress(): 100 }])
|
||||
# Biggest difference compared to Bitcoin is that we have explicit fee outputs,
|
||||
# which may be set in `createrawtransaction`. These will be added or adjusted
|
||||
# by `fundrawtransaction` to make the transaction balance.
|
||||
rawtx2 = e1.createrawtransaction(
|
||||
[],
|
||||
[{ e2.getnewaddress(): 100 }, { e1.getnewaddress() :5 }, { "fee": Decimal("0.1") }],
|
||||
)
|
||||
# Fee outputs are unblinded, with a scriptPubKey of "". On Elements, empty
|
||||
# scriptPubKeys are unspendable.
|
||||
|
||||
# Next we can fund the transaction (and replaces fee with something more appropriate)
|
||||
fundedtx = e1.fundrawtransaction(rawtx2)
|
||||
|
||||
# Blind
|
||||
blindedtx = e1.blindrawtransaction(fundedtx["hex"])
|
||||
# *Warning*: Raw blinding logic can be quite complicated, requiring the use of `ignoreblindfails`
|
||||
# to avoid having calls fail without manually inspecting transactions in great detail.
|
||||
# In general any transaction with 2 or more outputs to blind should succeed, so adding additional
|
||||
# is one strategy to resolve this.
|
||||
|
||||
# In some cases, such as when there is one blinded output but no blinded inputs,
|
||||
# blinding will fail. The `ignoreblindfails` option to `blindrawtransaction` may
|
||||
# be set, in which case the transaction will "successfully" not be blinded.
|
||||
#
|
||||
# To unconditionally blind a transaction, ensure that it has 2 or more outputs,
|
||||
# which will ensure that blinding is (a) useful and (b) mathematically possible.
|
||||
|
||||
# Sign
|
||||
signedtx = e1.signrawtransactionwithwallet(blindedtx)
|
||||
|
|
@ -575,18 +821,5 @@ signedtx = e1.signrawtransactionwithwallet(blindedtx)
|
|||
# And send
|
||||
txid = e1.sendrawtransaction(signedtx["hex"])
|
||||
|
||||
sync_all(e1, e2)
|
||||
|
||||
e2.gettransaction(txid)
|
||||
|
||||
# ADVANCED OPTIONS
|
||||
# rawblindrawtransaction : blind a raw transaction with no access to a wallet
|
||||
# -policyasset=<hex> : set network fee asset type to something other than BTC
|
||||
|
||||
bitcoin.stop()
|
||||
e1.stop()
|
||||
e2.stop()
|
||||
time.sleep(2)
|
||||
shutil.rmtree(e1_datadir)
|
||||
shutil.rmtree(e2_datadir)
|
||||
print ("Finished!")
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,19 @@ regtest=1
|
|||
txindex=1
|
||||
# We are spawning these inside a Python script which will manage them, so don't daemonize
|
||||
daemon=0
|
||||
# Extra debugging output in case things go wrong
|
||||
debug=1
|
||||
debugexclude=libevent
|
||||
debugexclude=leveldb
|
||||
printtoconsole=0
|
||||
|
||||
rpcuser=user3
|
||||
rpcpassword=password3
|
||||
|
||||
# Set a fallback fee, since initially the nodes will have no transaction data
|
||||
# to do fee estimation from
|
||||
fallbackfee=0.0002
|
||||
|
||||
[regtest]
|
||||
rpcport=18888
|
||||
port=18889
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ daemon=0
|
|||
listen=1
|
||||
# Just for looking at random txs
|
||||
txindex=1
|
||||
# Extra debugging output in case things go wrong
|
||||
debug=1
|
||||
debugexclude=libevent
|
||||
debugexclude=leveldb
|
||||
printtoconsole=0
|
||||
|
||||
# This is the script that controls pegged in funds in Bitcoin network
|
||||
# Users will be pegging into a P2SH of this, and the "watchmen"
|
||||
|
|
@ -39,9 +44,11 @@ mainchainrpcpassword=password3
|
|||
# Free money to make testing easier
|
||||
initialfreecoins=2100000000000000
|
||||
|
||||
# Set a fallback fee, since initially the nodes will have no transaction data
|
||||
# to do fee estimation from
|
||||
fallbackfee=0.0002
|
||||
|
||||
[elementsregtest]
|
||||
rpcport=18884
|
||||
port=18886
|
||||
|
||||
# Over p2p we will only connect to local other elementsd
|
||||
connect=localhost:18887
|
||||
|
|
|
|||
|
|
@ -7,6 +7,11 @@ rpcpassword=password2
|
|||
daemon=0
|
||||
listen=1
|
||||
txindex=1
|
||||
# Extra debugging output in case things go wrong
|
||||
debug=1
|
||||
debugexclude=libevent
|
||||
debugexclude=leveldb
|
||||
printtoconsole=0
|
||||
|
||||
#fedpegscript=51<pubkey>51ae
|
||||
#signblockscript=51<pubkey2>51ae
|
||||
|
|
@ -18,8 +23,11 @@ validatepegin=1
|
|||
|
||||
initialfreecoins=2100000000000000
|
||||
|
||||
# Set a fallback fee, since initially the nodes will have no transaction data
|
||||
# to do fee estimation from
|
||||
fallbackfee=0.0002
|
||||
|
||||
[elementsregtest]
|
||||
rpcport=18885
|
||||
port=18887
|
||||
connect=localhost:18886
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue