Skip to content

Commit

Permalink
add updated fortuna logic into the mine validator
Browse files Browse the repository at this point in the history
left a note on how pools are made possible
  • Loading branch information
MicroProofs committed Sep 8, 2023
1 parent 9de0a74 commit 45676d0
Show file tree
Hide file tree
Showing 2 changed files with 265 additions and 20 deletions.
4 changes: 2 additions & 2 deletions lib/fortuna/utils.ak
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use aiken/transaction.{
use aiken/transaction/credential.{ScriptCredential}
use aiken/transaction/value.{AssetName, PolicyId, Value}

pub fn find_input_resolved(
pub fn resolve_output_reference(
inputs: List<Input>,
output_ref: OutputReference,
) -> Output {
Expand All @@ -17,7 +17,7 @@ pub fn find_input_resolved(
if input.output_reference == output_ref {
input.output
} else {
find_input_resolved(inputs, output_ref)
resolve_output_reference(inputs, output_ref)
}
}

Expand Down
281 changes: 263 additions & 18 deletions validators/hard_fork.ak
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use aiken/builtin
use aiken/dict
use aiken/hash
use aiken/transaction.{InlineDatum,
OutputReference, ScriptContext, Transaction} as tx
use aiken/transaction/credential.{Inline, ScriptCredential}
use aiken/transaction/value
use aiken/hash.{blake2b_256, sha2_256}
use aiken/interval.{Finite, Interval, IntervalBound}
use aiken/list
use aiken/transaction.{
InlineDatum, Mint, Output, OutputReference, ScriptContext, Spend, Transaction,
} as tx
use aiken/transaction/credential.{Address, Inline, ScriptCredential}
use aiken/transaction/value.{tokens}
use fortuna.{master_token_name, token_name}
use fortuna/parameters.{epoch_number, halving_number, initial_payout}
use fortuna/types.{State}
use fortuna/utils.{list_at, quantity_of}

Expand All @@ -23,15 +27,254 @@ type Miner {
type MineAction {
nonce: ByteArray,
miner: Miner,
nft_input_ref: Option<OutputReference>,
}

validator(fortuna_v1_hash: ByteArray, fork_hash: ByteArray) {
type TargetState {
nonce: ByteArray,
epoch_time: Int,
block_number: Int,
current_hash: ByteArray,
leading_zeros: Int,
difficulty_number: Int,
miner: ByteArray,
}

validator(init_utxo_ref: OutputReference, fork_hash: ByteArray) {
fn tuna(redeemer: TunaAction, ctx: ScriptContext) -> Bool {
todo
when redeemer is {
Genesis -> {
// This time genesis mints "lord tuna" and hands it over to the hard fork contract
// Then after the hard fork "lord tuna" is handed over to this miner contract
// This allows us to maintain the miner contract with the same logic as the old one
let _x = 1
todo
}
Mine -> {
expect ScriptContext { transaction: tx, purpose: Mint(own_policy) } =
ctx

let own_credential = ScriptCredential(own_policy)
let own_address =
Address { payment_credential: own_credential, stake_credential: None }

// Mint(0) Mine requirement: Contract has one spend input with the policy as the payment credential
list.any(tx.inputs, fn(input) { input.output.address == own_address })?
}
Redeem -> todo
}
}

fn mine(datum: State, redeemer: MineAction, ctx: ScriptContext) -> Bool {
todo
// Access transaction information
let State {
block_number,
current_hash,
leading_zeros,
difficulty_number,
epoch_time,
current_posix_time,
interlink,
..
} = datum

let ScriptContext { transaction, purpose } = ctx

expect Spend(own_reference) = purpose

let Transaction { inputs, outputs, mint, validity_range, .. } = transaction

let mint = value.from_minted_value(mint)

let own_input = fortuna.own_validator_input_utxo(inputs, own_reference)

let Output { address: in_address, value: in_value, .. } = own_input

let credential = in_address.payment_credential

expect ScriptCredential(own_validator_hash) = credential

let MineAction { nonce, miner, nft_input_ref } = redeemer

// Spend(0) requirement: Contract has only one output going back to itself
expect [own_output] =
list.filter(outputs, fn(output: Output) { output.address == in_address })

let Output { datum: out_datum, value: out_value, .. } = own_output

// Time Range Span is 3 minutes or less
let Interval {
upper_bound: IntervalBound {
bound_type: upper_range,
is_inclusive: upper_is_inclusive,
},
lower_bound: IntervalBound {
bound_type: lower_range,
is_inclusive: lower_is_inclusive,
},
} = validity_range

// We have a constant expectation of the transaction time range
expect Finite(upper_range) = upper_range
expect Finite(lower_range) = lower_range
let averaged_current_time = ( upper_range - lower_range ) / 2 + lower_range

// Posix time is in milliseconds
// Spend(1) requirement: Time range span is 3 minutes or less and inclusive
expect and {
!upper_is_inclusive?,
lower_is_inclusive?,
(upper_range - lower_range <= 180000)?,
}
//
// In case you are wondering here is what enables pools
// A miner can be a pkh or an nft
// Nfts can come from any input, even validators
// So any validator logic can be enforced to run along with fortuna
expect
when miner is {
Pkh(signer) -> list.has(transaction.extra_signatories, signer)
Nft(nft_policy, nft_name) -> {
expect Some(input_ref) = nft_input_ref
//
let quantity =
utils.resolve_output_reference(inputs, input_ref).value
|> quantity_of(nft_policy, nft_name)
//
// Spend(2) requirement: Input has nft
quantity == 1
}
}
//
// Target state now includes a miner credential
let target =
TargetState {
nonce,
epoch_time,
block_number,
current_hash,
leading_zeros,
difficulty_number,
miner: blake2b_256(builtin.serialise_data(miner)),
}

let found_bytearray =
target
|> builtin.serialise_data()
|> sha2_256()
|> sha2_256()

let (found_difficulty_number, found_leading_zeros) =
fortuna.format_found_bytearray(found_bytearray)

// Mining Difficulty Met
// Spend(2) requirement: Found difficulty is less than or equal to the current difficulty
// We do this by checking the leading zeros and the difficulty number
expect or {
(found_leading_zeros > leading_zeros)?,
and {
(found_leading_zeros == leading_zeros)?,
(found_difficulty_number < difficulty_number)?,
},
}
//
// Spend(3) requirement: Input has master token
expect
(quantity_of(in_value, own_validator_hash, fortuna.master_token_name) == 1)?
//
// Spend(4) requirement: Only one type of token minted under the validator policy
expect [(token_name, quantity)] =
mint
|> tokens(own_validator_hash)
|> dict.to_list

let halving_exponent = block_number / halving_number

let expected_quantity =
if halving_exponent > 29 {
0
} else {
initial_payout / fortuna.two_exponential(halving_exponent)
}

// Spend(5) requirement: Minted token is the correct name and amount
expect and {
(token_name == fortuna.token_name)?,
(quantity == expected_quantity)?,
}
//
// Spend(6) requirement: Output has only master token and ada
expect
fortuna.value_has_only_master_and_lovelace(out_value, own_validator_hash)?
// Check output datum contains correct epoch time, block number, hash, and leading zeros
// Check for every divisible by 2016 block:
// - Epoch time resets
// - leading zeros is adjusted based on percent of hardcoded target time for 2016 blocks vs epoch time
expect InlineDatum(output_datum) = out_datum
// Spend(7) requirement: Expect Output Datum to be of type State
expect State {
block_number: out_block_number,
current_hash: out_current_hash,
leading_zeros: out_leading_zeros,
difficulty_number: out_difficulty_number,
epoch_time: out_epoch_time,
current_posix_time: out_current_posix_time,
interlink: out_interlink,
extra,
}: State = output_datum

// Spend(8) requirement: Check output has correct difficulty number, leading zeros, and epoch time
expect
if block_number % epoch_number == 0 && block_number > 0 {
// use total epoch time with target epoch time to get difficulty adjustment ratio
// ratio maxes out at 4/1 and mins to 1/4
let total_epoch_time =
epoch_time + averaged_current_time - current_posix_time
let (adjustment_numerator, adjustment_denominator) =
fortuna.get_difficulty_adjustment(total_epoch_time)
// Now use ratio to find new leading zeros difficulty
let (new_difficulty, new_leading_zeroes) =
fortuna.get_new_difficulty(
difficulty_number,
leading_zeros,
adjustment_numerator,
adjustment_denominator,
)
//
and {
(new_leading_zeroes == out_leading_zeros)?,
(new_difficulty == out_difficulty_number)?,
(0 == out_epoch_time)?,
}
} else {
let new_epoch_time =
epoch_time + averaged_current_time - current_posix_time
//
and {
(leading_zeros == out_leading_zeros)?,
(difficulty_number == out_difficulty_number)?,
(new_epoch_time == out_epoch_time)?,
}
}
//
// Spend(9) requirement: Output posix time is the averaged current time
expect (out_current_posix_time == averaged_current_time)?
//
// Spend(10) requirement: Output block number is the input block number + 1
// Spend(11) requirement: Output current hash is the target hash
expect
(block_number + 1 == out_block_number && out_current_hash == found_bytearray)?
//Spend(12) requirement: Check output extra field is within a certain size
expect (builtin.length_of_bytearray(builtin.serialise_data(extra)) <= 512)?
// Spend(13) requirement: Check output interlink is correct
(fortuna.calculate_interlink(
interlink,
found_bytearray,
found_leading_zeros,
found_difficulty_number,
difficulty_number,
leading_zeros,
) == out_interlink)?
}
}

Expand All @@ -42,11 +285,7 @@ type HardForkStatus {
}

type ForkDatum {
HardForkState {
status: HardForkStatus,
miner_support: Int,
fortuna_v1_height: Int,
}
HardForkState { status: HardForkStatus, fortuna_v1_height: Int }
MinerGlobalLockState { miner_locked_tuna: Int }
GlobalLockState { locked_tuna: Int }
NftState { nft_key: ByteArray }
Expand Down Expand Up @@ -86,7 +325,7 @@ const hard_fork_state_token: ByteArray = "hfs"

// maybe we should do time instead of block height?
validator(
utxo_ref: OutputReference,
init_utxo_ref: OutputReference,
fortuna_v1_hash: ByteArray,
block_height: Int,
) {
Expand Down Expand Up @@ -121,7 +360,10 @@ validator(
lock_state_ref,
} -> {
let hard_fork_state_input =
utils.find_input_resolved(reference_inputs, hard_fork_state_ref)
utils.resolve_output_reference(
reference_inputs,
hard_fork_state_ref,
)

expect
utils.value_has_nft_and_lovelace(
Expand Down Expand Up @@ -186,7 +428,7 @@ validator(
expect InlineDatum(out_lock_datum) = lock_output.datum

let lock_input =
utils.find_input_resolved(inputs, lock_state_ref)
utils.resolve_output_reference(inputs, lock_state_ref)

when lock_type is {
// In miner lock we also check for the presence of the fortuna v1 script
Expand Down Expand Up @@ -215,7 +457,10 @@ validator(
}: ForkDatum = out_lock_datum

let tuna_v1_input =
utils.find_input_resolved(inputs, fortuna_v1_input_ref)
utils.resolve_output_reference(
inputs,
fortuna_v1_input_ref,
)

expect
quantity_of(
Expand Down Expand Up @@ -273,7 +518,7 @@ validator(

let Transaction { inputs, withdrawals, .. } = transaction

let own_input = utils.find_input_resolved(inputs, own_ref)
let own_input = utils.resolve_output_reference(inputs, own_ref)

let own_withdrawal = Inline(own_input.address.payment_credential)

Expand Down

0 comments on commit 45676d0

Please sign in to comment.