Delegated PoS block header structure
Size |
Description |
Data type |
Comments |
4 |
version |
int32_t |
Block version, typically 4. |
32 |
prev_block |
char[32] |
The previous block hash. |
32 |
merkle_root |
char[32] |
The Merkle tree root of the block's transactions. |
4 |
timestamp |
uint32_t |
Block creation timestamp, the 4 least significant bits must be set to 0. Must be greater than the previous block's timestamp. |
4 |
bits |
uint32_t |
The difficulty of this block. |
4 |
nonce |
uint32_t |
A nonce, unused in PoS blocks and therefore usually set to 0. |
32 |
state_root |
char[32] |
The root of the state trie |
32 |
utxo_root |
char[32] |
The root of the contract utxo balances trie. |
32 |
stake_prevout_hash |
char[32] |
The delegated staking utxo's hash |
4 |
staking_prevout_vout |
uint32_t |
The delegated staking utxo's vout index |
131 |
blockSig |
varlength |
The block's signature created by the staker concatenated with the PoD created by the delegator during delegation. Note that the field will be prefixed by a 0x82 byte specifying the length. |
addDelegation raw transaction
{'blockhash': '2faf179a5253081fabc531bd10f9705502621b15975d841665dbeb8b310f6335',
'blocktime': 1610453886,
'confirmations': 1,
'hash': '51dfe41dc303b99b860c5aa44adabb41bf1de371a52e3204c89fa52b02f10dca',
'hex': '010000000100647301a87adbb2eb5e72b29f334798574ff1e9f520a9a9c1125ff20cdf01c6000000006a47304402205240e8420b5d1f868c9d54f172edb911966ed7ff91d646e4437b442ee4c44da102204214e6248ff2703fca45f311bc043da3cc4295e05f32b05228887a5b8a66e87c01210390724f049390d99491f805c887d87841e71a22e53f01ccbf38bad074276520b100000000020000000000000000e501040310552201284cc54c0e968c0000000000000000000000007da6be640325a6048cd67f070c314fa405df126b00000000000000000000000000000000000000000000000000000000000000630000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000004120465e98e1dc7f7fa4bdef6bd1775e27cc86e195cd948b28f61bd7cfb9a613247c31032df1dccf43edf24228b942c337e420b06c196a24605e54b75bfd9e16e69e140000000000000000000000000000000000000086c280d5eca3d10100001976a9144008700e91bba17d858722d7112adf436ca416ed88ac00000000',
'locktime': 0,
'size': 429,
'time': 1610453886,
'txid': '51dfe41dc303b99b860c5aa44adabb41bf1de371a52e3204c89fa52b02f10dca',
'version': 1,
'vin': [{'scriptSig': {'asm': '304402205240e8420b5d1f868c9d54f172edb911966ed7ff91d646e4437b442ee4c44da102204214e6248ff2703fca45f311bc043da3cc4295e05f32b05228887a5b8a66e87c[ALL] '
'0390724f049390d99491f805c887d87841e71a22e53f01ccbf38bad074276520b1',
'hex': '47304402205240e8420b5d1f868c9d54f172edb911966ed7ff91d646e4437b442ee4c44da102204214e6248ff2703fca45f311bc043da3cc4295e05f32b05228887a5b8a66e87c01210390724f049390d99491f805c887d87841e71a22e53f01ccbf38bad074276520b1'},
'sequence': 0,
'txid': 'c601df0cf25f12c1a9a920f5e9f14f579847339fb2725eebb2db7aa801736400',
'vout': 0}],
'vout': [{'n': 0,
'scriptPubKey': {'asm': '4 2250000 40 '
'4c0e968c0000000000000000000000007da6be640325a6048cd67f070c314fa405df126b00000000000000000000000000000000000000000000000000000000000000630000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000004120465e98e1dc7f7fa4bdef6bd1775e27cc86e195cd948b28f61bd7cfb9a613247c31032df1dccf43edf24228b942c337e420b06c196a24605e54b75bfd9e16e69e '
'0000000000000000000000000000000000000086 '
'OP_CALL',
'hex': '01040310552201284cc54c0e968c0000000000000000000000007da6be640325a6048cd67f070c314fa405df126b00000000000000000000000000000000000000000000000000000000000000630000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000004120465e98e1dc7f7fa4bdef6bd1775e27cc86e195cd948b28f61bd7cfb9a613247c31032df1dccf43edf24228b942c337e420b06c196a24605e54b75bfd9e16e69e140000000000000000000000000000000000000086c2',
'type': 'call'},
'value': Decimal('0E-8')},
{'n': 1,
'scriptPubKey': {'addresses': ['qPPxWa7P2dk6TW1QEwmutwMwSPo6Fw51sB'],
'asm': 'OP_DUP OP_HASH160 '
'4008700e91bba17d858722d7112adf436ca416ed '
'OP_EQUALVERIFY OP_CHECKSIG',
'hex': '76a9144008700e91bba17d858722d7112adf436ca416ed88ac',
'reqSigs': 1,
'type': 'pubkeyhash'},
'value': Decimal('19999.10000000')}],
'vsize': 429,
'weight': 1716}
removeDelegation transaction
{'blockhash': '2641e2e6582a720476a6617834b88bc11f51aed5e8fbd1c07d39cb06b9676b21',
'blocktime': 1610453886,
'confirmations': 1,
'hash': 'e2fa242270373b3b7bc42b5292fb7babada277781c22fc9d8827941a1a09b770',
'hex': '0100000001009f8abbe57e7e594775241884dc2fc9d393199b106daff90768dc00d4e75153000000006a47304402203da7a5ef757547dace39856be8e7a36a94d346052d4b9e005b7a42a1db371626022017522913bd341532f983fc3e591c062736d594c2e1da68a21aaaae9d422e74d701210390724f049390d99491f805c887d87841e71a22e53f01ccbf38bad074276520b1000000000200000000000000002301040350c3000128043d666e8b140000000000000000000000000000000000000086c2809b2ba9d10100001976a9144008700e91bba17d858722d7112adf436ca416ed88ac00000000',
'locktime': 0,
'size': 235,
'time': 1610453886,
'txid': 'e2fa242270373b3b7bc42b5292fb7babada277781c22fc9d8827941a1a09b770',
'version': 1,
'vin': [{'scriptSig': {'asm': '304402203da7a5ef757547dace39856be8e7a36a94d346052d4b9e005b7a42a1db371626022017522913bd341532f983fc3e591c062736d594c2e1da68a21aaaae9d422e74d7[ALL] '
'0390724f049390d99491f805c887d87841e71a22e53f01ccbf38bad074276520b1',
'hex': '47304402203da7a5ef757547dace39856be8e7a36a94d346052d4b9e005b7a42a1db371626022017522913bd341532f983fc3e591c062736d594c2e1da68a21aaaae9d422e74d701210390724f049390d99491f805c887d87841e71a22e53f01ccbf38bad074276520b1'},
'sequence': 0,
'txid': '5351e7d400dc6807f9af6d109b1993d3c92fdc8418247547597e7ee5bb8a9f00',
'vout': 0}],
'vout': [{'n': 0,
'scriptPubKey': {'asm': '4 100000 40 -191784509 '
'0000000000000000000000000000000000000086 '
'OP_CALL',
'hex': '01040350c3000128043d666e8b140000000000000000000000000000000000000086c2',
'type': 'call'},
'value': Decimal('0E-8')},
{'n': 1,
'scriptPubKey': {'addresses': ['qPPxWa7P2dk6TW1QEwmutwMwSPo6Fw51sB'],
'asm': 'OP_DUP OP_HASH160 '
'4008700e91bba17d858722d7112adf436ca416ed '
'OP_EQUALVERIFY OP_CHECKSIG',
'hex': '76a9144008700e91bba17d858722d7112adf436ca416ed88ac',
'reqSigs': 1,
'type': 'pubkeyhash'},
'value': Decimal('19999.96000000')}],
'vsize': 235,
'weight': 940}
coinstake transaction for delegated PoS block
{'blockhash': '739a79af31febfee43bb2e3b3acf65231d58eadd76092a10a4e6354a426d2661',
'blocktime': 1610453888,
'confirmations': 1,
'hash': '9ac332299c26b07b6b666d29f4c9e10fc66f92a854068591b47e3d88adf0c1fe',
'hex': '01000000010ce0e34187d786b1512cb0ddb29c58886664619560c3eaff6adac2253a76ef27000000006a47304402206712c2c824d2ddc08228a2df0462816f87d1240d95bf3549826a97bda536a58102205c36d24a2092cd3ec70e66accf2c3051ac9fa5d0f92d14801de8a76c7e960fa101210225a9966a7184dc0542bf8c24ced4a0fab4080f9331e683773d2df138fce6fee2000000000300000000000000000000787caa9e03000023210225a9966a7184dc0542bf8c24ced4a0fab4080f9331e683773d2df138fce6fee2ac00c817a8040000001976a9144008700e91bba17d858722d7112adf436ca416ed88ac00000000',
'locktime': 0,
'size': 244,
'time': 1610453888,
'txid': '9ac332299c26b07b6b666d29f4c9e10fc66f92a854068591b47e3d88adf0c1fe',
'version': 1,
'vin': [{'scriptSig': {'asm': '304402206712c2c824d2ddc08228a2df0462816f87d1240d95bf3549826a97bda536a58102205c36d24a2092cd3ec70e66accf2c3051ac9fa5d0f92d14801de8a76c7e960fa1[ALL] '
'0225a9966a7184dc0542bf8c24ced4a0fab4080f9331e683773d2df138fce6fee2',
'hex': '47304402206712c2c824d2ddc08228a2df0462816f87d1240d95bf3549826a97bda536a58102205c36d24a2092cd3ec70e66accf2c3051ac9fa5d0f92d14801de8a76c7e960fa101210225a9966a7184dc0542bf8c24ced4a0fab4080f9331e683773d2df138fce6fee2'},
'sequence': 0,
'txid': '27ef763a25c2da6affeac3609561646688589cb2ddb02c51b186d78741e3e00c',
'vout': 0}],
'vout': [{'n': 0,
'scriptPubKey': {'asm': '', 'hex': '', 'type': 'nonstandard'},
'value': Decimal('0E-8')},
{'n': 1,
'scriptPubKey': {'asm': '0225a9966a7184dc0542bf8c24ced4a0fab4080f9331e683773d2df138fce6fee2 '
'OP_CHECKSIG',
'hex': '210225a9966a7184dc0542bf8c24ced4a0fab4080f9331e683773d2df138fce6fee2ac',
'type': 'pubkey'},
'value': Decimal('103.96000000')},
{'n': 2,
'scriptPubKey': {'addresses': ['qPPxWa7P2dk6TW1QEwmutwMwSPo6Fw51sB'],
'asm': 'OP_DUP OP_HASH160 '
'4008700e91bba17d858722d7112adf436ca416ed '
'OP_EQUALVERIFY OP_CHECKSIG',
'hex': '76a9144008700e91bba17d858722d7112adf436ca416ed88ac',
'reqSigs': 1,
'type': 'pubkeyhash'},
'value': Decimal('0.04000000')}],
'vsize': 244,
'weight': 976}
Test case for adding delegation, submitting a valid delegated block and removing delegation
#!/usr/bin/env python3
from test_framework.test_framework import BitcoinTestFramework
from test_framework.messages import *
from test_framework.script import *
from test_framework.mininode import *
from test_framework.xray import *
from test_framework.xrayconfig import *
from test_framework.util import *
import time
import base64
import pprint
pp = pprint.PrettyPrinter()
def signmessage(key, msg):
sighash = hash256(b"\x15xray Signed Message:\n\x28"+msg.encode('ascii'))
sig = key.sign_ecdsa(sighash, low_s=True, der_sig=False)
return sig
def signdelegatedblock(key, block, pod):
sigdata = b""
sigdata += struct.pack("<i", block.nVersion)
sigdata += ser_uint256(block.hashPrevBlock)
sigdata += ser_uint256(block.hashMerkleRoot)
sigdata += struct.pack("<I", block.nTime)
sigdata += struct.pack("<I", block.nBits)
sigdata += struct.pack("<I", block.nNonce)
sigdata += ser_uint256(block.hashStateRoot)
sigdata += ser_uint256(block.hashUTXORoot)
sigdata += block.prevoutStake.serialize()
sigdata += struct.pack("<b", len(pod)) + pod
blocksig = key.sign_ecdsa(hash256(sigdata), low_s=True, der_sig=False)
return blocksig
class xraySimpleDelegationContractTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
self.extra_args = [['-txindex=1', '-logevents=1'], ['-txindex=1', '-logevents=1']]
def run_test(self):
mocktime = 1610443886
for n in self.nodes: n.setmocktime(mocktime)
self.delegator = self.nodes[0]
delegator_address = "qPPxWa7P2dk6TW1QEwmutwMwSPo6Fw51sB"
delegator_address_hex = "4008700e91bba17d858722d7112adf436ca416ed"
delegator_privkey = "cQA8JyNZc4YTsvV44Y6HFQMLfRTfS49rSDMSuNSgRauwtBCj9ieq"
delegator_eckey = wif_to_ECKey(delegator_privkey)
self.delegator.importprivkey(delegator_privkey)
self.staker = self.nodes[1]
staker_address = "qV1mNZokPVcrQcH8RdnfzHsVVyVSGFnaue"
staker_address_hex = "7da6be640325a6048cd67f070c314fa405df126b"
staker_privkey = "cNYezxiDtCu5DMUjqAv31tnGAMMi3Kh7xLYFR7tWUCvbSdMz78N8"
staker_eckey = wif_to_ECKey(staker_privkey)
self.staker.importprivkey(staker_privkey)
self.staker.generatetoaddress(1, staker_address)
self.sync_all()
self.delegator.generatetoaddress(COINBASE_MATURITY+100, delegator_address)
self.sync_all()
self.staker.generatetoaddress(COINBASE_MATURITY, staker_address)
self.sync_all()
for n in self.nodes: n.setmocktime(mocktime+10000)
offline_staking_contract_address = bytes.fromhex("0000000000000000000000000000000000000086")
pod = signmessage(delegator_eckey, staker_address_hex)
calldata = bytes.fromhex("4c0e968c") # function abi
calldata += b'\x00'*12 + bytes.fromhex(staker_address_hex)
calldata += b'\x00'*31 + b'\x63'
calldata += b'\x00'*31 + b'\x60'
calldata += b'\x00'*31 + b'\x41'
calldata += pod
contract_call_script = CScript([b'\x04', CScriptNum(2250000), CScriptNum(40), calldata, offline_staking_contract_address, OP_CALL])
change_script = CScript([OP_DUP, OP_HASH160, bytes.fromhex(delegator_address_hex), OP_EQUALVERIFY, OP_CHECKSIG])
delegator_unspent = self.delegator.listunspent()[0]
delegator_unspent_outpoint = COutPoint(int(delegator_unspent['txid'], 16), delegator_unspent['vout'])
tx = CTransaction()
tx.vin = [CTxIn(delegator_unspent_outpoint)]
tx.vout.append(CTxOut(0, scriptPubKey=contract_call_script)) # The contract call
tx.vout.append(CTxOut(int(delegator_unspent['amount']*COIN) - 2250000*40, scriptPubKey=change_script)) # change output
tx = rpc_sign_transaction(self.delegator, tx)
txid = self.delegator.sendrawtransaction(tx.serialize().hex())
self.delegator.generatetoaddress(1, delegator_address)
# We have now added a delegation, let's stake a block for that delegation.
prevblock = self.delegator.getblock(self.delegator.getbestblockhash())
blocktime = (prevblock['time']+0x10) & 0xfffffff0
for delegator_unspent in self.delegator.listunspent():
if delegator_unspent['confirmations'] <= 500:
continue
delegator_utxo = COutPoint(int(delegator_unspent['txid'], 16), delegator_unspent['vout'])
txtime = self.delegator.getrawtransaction(delegator_unspent['txid'], True)['time']
target = uint256_from_compact(int(prevblock['bits'], 16))
data = b""
data += ser_uint256(int(prevblock['modifier'], 16))
data += struct.pack("<I", txtime)
data += delegator_utxo.serialize()
data += struct.pack("<I", blocktime)
kernel = uint256_from_str(hash256(data))
# valid block found?
if kernel <= (target*int(delegator_unspent['amount']*COIN)) & (2**256 - 1):
coinbase = create_coinbase(prevblock['height']+1)
coinbase.vout[0].nValue = 0
coinbase.vout[0].scriptPubKey = b""
coinbase.rehash()
block = create_block(int(prevblock['hash'], 16), coinbase, blocktime)
block.nTime = blocktime
block.hashStateRoot = int(prevblock['hashStateRoot'], 16)
block.hashUTXORoot = int(prevblock['hashUTXORoot'], 16)
block.prevoutStake = delegator_utxo
# Find an unspent staker tx with value > 100, we know that all unspent staker txs fulfill this requirement
staker_unspent = self.staker.listunspent()[0]
staker_subsidy = INITIAL_BLOCK_REWARD*COIN*99//100
staker_reward_script = CScript([staker_eckey.get_pubkey().get_bytes(), OP_CHECKSIG])
delegation_reward_script = CScript([OP_DUP, OP_HASH160, bytes.fromhex(delegator_address_hex), OP_EQUALVERIFY, OP_CHECKSIG])
coinstake_tx = CTransaction()
coinstake_tx.vin = [CTxIn(COutPoint(int(staker_unspent['txid'], 16), staker_unspent['vout']))]
coinstake_tx.vout.append(CTxOut())
coinstake_tx.vout.append(CTxOut(staker_subsidy + int(staker_unspent['amount']*COIN), scriptPubKey=staker_reward_script))
coinstake_tx.vout.append(CTxOut(int(INITIAL_BLOCK_REWARD*COIN-staker_subsidy), scriptPubKey=delegation_reward_script))
coinstake_tx = rpc_sign_transaction(self.staker, coinstake_tx)
block.vtx.append(coinstake_tx)
block.hashMerkleRoot = block.calc_merkle_root()
# create the signature
blocksig = signdelegatedblock(staker_eckey, block, pod)
block.vchBlockSig = blocksig + pod
block.rehash()
self.staker.submitblock(block.serialize().hex())
break
# Finally remove the delegation
calldata = bytes.fromhex("3d666e8b") # function abi
contract_call_script = CScript([b'\x04', CScriptNum(100000), CScriptNum(40), calldata, offline_staking_contract_address, OP_CALL])
change_script = CScript([OP_DUP, OP_HASH160, bytes.fromhex(delegator_address_hex), OP_EQUALVERIFY, OP_CHECKSIG])
delegator_unspent = self.delegator.listunspent()[0]
delegator_unspent_outpoint = COutPoint(int(delegator_unspent['txid'], 16), delegator_unspent['vout'])
tx = CTransaction()
tx.vin = [CTxIn(delegator_unspent_outpoint)]
tx.vout.append(CTxOut(0, scriptPubKey=contract_call_script)) # The contract call
tx.vout.append(CTxOut(int(delegator_unspent['amount']*COIN) - 100000*40, scriptPubKey=change_script)) # change output
tx = rpc_sign_transaction(self.delegator, tx)
txid = self.delegator.sendrawtransaction(tx.serialize().hex())
self.delegator.generatetoaddress(1, delegator_address)
if __name__ == '__main__':
xraySimpleDelegationContractTest().main()