-
Notifications
You must be signed in to change notification settings - Fork 671
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/get pox addrs #3245
Feat/get pox addrs #3245
Conversation
… the .pox or .pox-2 contracts, and add a trait method for getting the PoX addresses and payouts sent in a particular burn block height on the calling Stacks fork
…method for pulling the requisite information out of the BurnStateDB impl and converts it into a Clarity value)
…hain recipients; also, make prepare phase calculation a static method
…sses for this sortition as well as the per-output payout (which will then be reported via BurnStateDB implementations by querying the MARF)
…tacksAddress to represent PoX reward addresses
…ase58 strings and Bitcoin txouts
… other tests to use PoxAddress instead of StacksAddress)
…put to `stack-stx` from on-chain StackStxOp
…ithout uncommitted state from the underlying storage connection
…torage transaction state with only an immutable reference (required to make it possible to query the sortition DB MARF within a BurnStateDB impl)
…ed writes are not visible); instead, pass an immutable reference to the tx's db handle
pub enum PoxAddress { | ||
/// represents { version: (buff u1), hashbytes: (buff 20) }. | ||
/// The address hash mode is optional because if we decode a legacy bitcoin address, we won't | ||
/// be able to determine the hash mode since we can't distinguish segwit-p2sh from p2sh | ||
Standard(StacksAddress, Option<AddressHashMode>), | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this struct should be in the chainstate
module, rather than in the Clarity module. I know there's some leakage of PoX into the Clarity module (mostly in the function signatures in the database), but most of these methods seem more important to the chainstate than it is to Clarity -- all Clarity really needs is the tuples:
/// Returns the paid out PoX addresses (as Clarity tuples of type `{ hashbytes: (buff 1), version: (buff 20) }` )
pub fn get_pox_payout_addrs_for_burnchain_height(
&mut self,
burnchain_block_height: u32,
) -> Option<(Vec<TupleData>, u128)> {
Whereas the chainstate module actually uses most of the methods of this struct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I can do that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done: 49dea6e
…ve BurnStateDB::get_pox_payout_addrs() return Option<Vec<TupleData>, u128> instead of Option<Vec<PoxAddress>, u128>. This makes more sense since the chainstate module depends on PoxAddress far more so than Clarity. So, have the BurnStateDB return the tuple representation of a PoxAddress, and have the chainstate module convert that tuple into a PoxAddress once needed.
pub fn pox_reward_set_payouts_key() -> String { | ||
format!("sortition_db::reward_set::payouts") | ||
} | ||
|
||
pub fn pox_reward_set_payouts_value(addrs: Vec<PoxAddress>, payout_per_addr: u128) -> String { | ||
serde_json::to_string(&(addrs, payout_per_addr)).unwrap() | ||
} | ||
|
||
pub fn pox_reward_set_payouts_decode(addr_str: &str) -> (Vec<PoxAddress>, u128) { | ||
let addrs_and_payout: (Vec<PoxAddress>, u128) = serde_json::from_str(addr_str).unwrap(); | ||
addrs_and_payout | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this value need to be stored in the MARF, or can it just be indexed by the SortitionID
in a sqlite table?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As in, lookups are always of the form what was the payout set at <sortition-id>
(which doesn't need to traverse the sortition history), and they're never what was the payout set at <sortition-id> or if there wasn't one, what was the last payout prior?
(which could only be answered through parent traversal).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose it doesn't need to be written to the MARF. I can just write it to a DB table -- it would make the I/O faster at least.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great to me, @jcnelson, but I think it may not be necessary to store the PoX payout information in the sortition MARF, but it could instead just be an entry in a sortition-id indexed table. It's up to you if you think the difference would be substantial enough to merit changes.
…, since we'll have an entry for every sortition
I think I might want to update the API for this to return the burn addresses as well (so then it's obvious how much total BTC was spent, given the per-output spend) |
Need to make the return type easier to understand:
|
Okay, the above is done. cc @kantai @gregorycoppola @pavitthrap for review/approval. |
…TS_PER_COMMIT (in the reward phase) or 1 (in the prepare phase)
…nd at the right time
… reported, and at the right time
let address = outputs[0] | ||
.address | ||
.clone() | ||
.try_into_stacks_address() | ||
.ok_or_else(|| { | ||
test_debug!("Invalid address: not representable as a StacksAddress"); | ||
op_error::InvalidInput | ||
})?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a consensus change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope. First, .address
isn't even used in LeaderKeyRegister
. I'm even going so far as to remove it in my segwit PR. Second, .try_into_stacks_address()
is infallible right now, since the only PoxAddress
variant (Standard(addr)
) contains a StacksAddress
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case, this should panic -- as it is currently written, it suggests that an error would invalidate the LeaderKeyRegister
(similarly for all the operations that this try_into_stacks_address()
method is invoked on).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason it's written this way is because .try_into_stacks_address()
will soon be fallible, i.e. on the arrival of the native segwit support. The reason it will be fallible is because there will not be a way to convert a segwit address into a principal
type, so parsing StackStxOp
and TransferStxOp
must fail here if the intended recipient can't be represented as a principal
.
src/chainstate/stacks/address.rs
Outdated
pub fn bytes(&self) -> Vec<u8> { | ||
match *self { | ||
PoxAddress::Standard(addr, _) => addr.bytes.0.to_vec(), | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's better to return a &[u8]
here. The caller can then decide if they want to copy to a vector or not. This gets used for sorting, so requiring a bunch of copies would be inefficient there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're referring for StacksChainState::make_reward_set()
, the problem with doing this the way you suggest is that the reference &addrs.bytes.0
doesn't live long enough in the closure passed to .sort_by_key()
. Maybe we should keep this as-is and use .sort_by_cached_key()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess that's fine to leave as is then.
/// A PoX address as seen by the .pox and .pox-2 contracts. | ||
/// Used by the sortition DB and chains coordinator to extract addresses from the PoX contract to | ||
/// build the reward set and to validate block-commits. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add "This comprises a larger set of possible addresses than StacksAddress
"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, will do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/chainstate/stacks/address.rs
Outdated
pub fn hashmode(&self) -> Option<AddressHashMode> { | ||
match *self { | ||
PoxAddress::Standard(_, hm) => hm.clone(), | ||
} | ||
} | ||
|
||
#[cfg(any(test, feature = "testing"))] | ||
pub fn version(&self) -> u8 { | ||
self.hashmode() | ||
.expect("FATAL: tried to load the hashmode of a PoxAddress which has none known") | ||
as u8 | ||
} | ||
|
||
pub fn bytes(&self) -> Vec<u8> { | ||
match *self { | ||
PoxAddress::Standard(addr, _) => addr.bytes.0.to_vec(), | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add rustdocs to the public functions of this struct? I think as it abstracts multiple kinds of addresses, functions like "bytes" are less self-explanatory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, will do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me, I think there's just some superficial comments remaining: slight tweaks to an interface and some additional rustdocs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
src/chainstate/stacks/address.rs
Outdated
} | ||
} | ||
|
||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove dead code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
This PR implements #3155 by adding
get-burn-block-info? pox-addrs <burn-height>
to Clarity. It returns anoptional
tuple with two items:addrs
: The list of up to two PoX addresses (type(list 2 { hashbytes: (buff 20), version: (buff 1) })
)payout
: The number of burnchain tokens (i.e. satoshis) paid to each PoX address across all block-commits (typeuint
).It returns
(some ..)
when the sortition forburn-height
is in the transaction's Stacks block's fork, andnone
if not.Burn addresses are not reported. This method will return an emptyEDIT: Burn addresses are reported -- up to two during the reward phase for unclaimed PoX reward slots, and one during the prepare phase.addrs
list during the reward phase if the BTC are to be burnt, and will return an emptyaddrs
list during the prepare phase.The
payout
value is per TX output. During the reward phase, there are two outputs whose values are equal to half of the node'sburn_fee_cap
. This is true even if there are no more PoX reward addresses to pay. During the prepare phase, there is one output, and its value is equal to the node'sburn_fee_cap
. So for example if the network is spending 10,000 sats per block-commit, thenpayout
will be 5,000 during the reward phase and 10,000 during the prepare phase.In addition to implementing this feature, this PR replaces the use of
StacksAddress
to represent Bitcoin transaction outputs with a similarPoxAddress
structure. This structure captures all of the fields in apox-addr
argument tostack-stx
ordelegate-stack-stx
, which is not recoverable from just the Bitcoin chain state. It is these values that are passed back to the caller fromget-burn-block-info? pox-addrs
.The addition of
PoxAddress
is not only necessary to represent the full PoX address data, but also lays the groundwork for implementing segwit (and taproot) addresses. These addresses cannot be represented in aStacksAddress
, but becausePoxAddress
is an enum, we can later add type variants that represent anyscriptPubKey
we want.