Merge pull request #70 from Xahau/governance_hook_l2

Governance hook l2
This commit is contained in:
RichardAH
2023-06-26 15:10:46 +02:00
committed by GitHub
38 changed files with 1472 additions and 284 deletions

View File

@@ -443,6 +443,7 @@ target_sources (rippled PRIVATE
src/ripple/app/tx/impl/SetRegularKey.cpp
src/ripple/app/tx/impl/SetHook.cpp
src/ripple/app/tx/impl/ClaimReward.cpp
src/ripple/app/tx/impl/GenesisMint.cpp
src/ripple/app/tx/impl/Import.cpp
src/ripple/app/tx/impl/Invoke.cpp
src/ripple/app/tx/impl/SetSignerList.cpp

View File

@@ -10,13 +10,20 @@
/**
* Xahau Governance Hook
*
* The governance model is a 20 seat round table.
* Each seat may be filled or empty (provided there is at least one filled seat.)
* The primary governance table sits on the genesis account. This is also called the L1 table.
* Seats at this table are called L1s or L1 members.
* At L1, for votes relating to table membership 80% of the filled seats must vote in favour.
* At L1, for votes relating to everything else 100% of the filled seats must vote in favour.
* One or more L1 seats may contain an account that has an L2 governance hook installed on it and is blackholed.
* This is referred to as an L2 table. The seats at the table are called L2s or L2 members.
* There may be multiple L2 tables.
*
* Hook Parameters:
* Parameter Name: {'I', 'M', 'C'}
* Parameter Value: Initial Member Count <1 byte>
*
* Parameter Name: {'I', 'D', 'A'}
* Parameter Value: Initial Distribution Amount to each initial member <8 byte BE drops>
*
* Parameter Name: {'I', 'R', 'R'}
* Parameter Value: Initial Reward Rate <8 byte XFL fraction between 0 and 1, LE>
*
@@ -29,7 +36,8 @@
* ...
*
* Topics:
* 'H[0-9]' - Hook Hash in positions 0-9 <32 byte hash>
* 'H[0-9]' - Hook Hash in positions 0-9 <32 byte hash> on genesis
* 'h[0-9]' - Hook Hash in positions 0-9 <32 byte hash> on local L2 table (when applicable)
* 'RR' - reward rate <le xfl 8 bytes>
* 'RD' - reward delay <le xfl 8 bytes>
* 'S[0-19]' - who is a governance member occupying that seat <20 byte accid>
@@ -66,14 +74,14 @@
*
* Parameter Name: {'T'}
* Parameter Value: The topic to vote on <2 bytes>
* { 'S|H' (seat, hook), '\0 + topic id' }, or
* { 'S|H' (seat L1, hook L1), '\0 + topic id' }, or
* { 'R' (reward), 'R|D' (rate, delay) }
*
* Parameter Name: {'V'}
* Parameter Value: The data to vote for this topic (accid, hook hash, reward rate/delay)
**/
#define SVAR(x) x, sizeof(x)
#define SVAR(x) &x, sizeof(x)
#define DONE(x)\
accept(SVAR(x),(uint32_t)__LINE__);
@@ -83,143 +91,113 @@
#define DEBUG 1
// genesis account id
uint8_t genesis[20] =
{0xB5U,0xF7U,0x62U,0x79U,0x8AU,0x53U,0xD5U,0x43U,0xA0U,0x14U,
0xCAU,0xF8U,0xB2U,0x97U,0xCFU,0xF8U,0xF2U,0xF9U,0x37U,0xE8U};
uint8_t zero32[32];
int64_t hook(uint32_t r)
{
_g(1,1);
if (otxn_type() != 99) // ttINVOKE
int64_t tt = otxn_type();
if (tt != 99) // ttINVOKE only
DONE("Governance: Passing non-Invoke txn. HookOn should be changed to avoid this.");
// get the account id
uint8_t account_field[32];
otxn_field(account_field + 12, 20, sfAccount);
uint8_t hook_accid[20];
hook_account(SBUF(hook_accid));
uint8_t hook_accid[32];
hook_account(hook_accid + 12, 20);
// outgoing txns to other hooks allowed
if (BUFFER_EQUAL_20(hook_accid + 12, account_field + 12))
{
uint8_t dest_acc[20];
if (otxn_field(SBUF(dest_acc), sfDestination) == 20 && !BUFFER_EQUAL_20(hook_accid + 12, dest_acc))
DONE("Goverance: Passing outgoing txn.");
}
int64_t is_L1 = BUFFER_EQUAL_20(hook_accid + 12, genesis);
if (!is_L1)
NOPE("Governance: This is the L1 governance hook. It can only be used on genesis account.");
int64_t member_count = state(0,0, "MC", 2);
if (DEBUG)
TRACEVAR(member_count);
// outgoing txns to other hooks allowed
// but a self ttINVOKE means rewards hook is trying to get the governance hook to distribute governance rewards
int64_t is_distribution = 0;
int64_t is_setup = member_count == DOESNT_EXIST;
if (BUFFER_EQUAL_20(hook_accid, account_field + 12))
{
uint8_t dest_acc[20];
if (otxn_field(SBUF(dest_acc), sfDestination) == 20 && !BUFFER_EQUAL_20(hook_accid, dest_acc))
DONE("Goverance: Passing outgoing txn.");
is_distribution = 1;
}
// initial execution, setup hook
int64_t distribution_amount;
if (is_setup)
if (member_count == DOESNT_EXIST)
{
// gather hook parameters
int64_t imc, ida, irr, ird;
uint8_t imc;
uint64_t irr, ird;
TRACEVAR(imc);
if (hook_param(SVAR(imc), "IMC", 3) < 0)
NOPE("Governance: Initial Member Count Parameter missing (IMC).");
if (hook_param(SVAR(ida), "IDA", 3) < 0)
NOPE("Governance: Initial Distribution Amount Prameter missing (IDA).");
if (hook_param(SVAR(irr), "IRR", 3) < 0)
NOPE("Governance: Initial Reward Rate Parameter missing (IRR).");
if (hook_param(SVAR(ird), "IRD", 3) < 0)
NOPE("Governance: Initial Reward Delay Parameter miss (IRD).");
// sanity check parameters
TRACEVAR(imc);
if (imc == 0)
NOPE("Governance: Initial Member Count must be > 0.");
if (imc > SEAT_COUNT)
NOPE("Governance: Initial Member Count must be <= Seat Count (20).");
if (hook_param(SVAR(irr), "IRR", 3) < 0)
NOPE("Governance: Initial Reward Rate Parameter missing (IRR).");
if (hook_param(SVAR(ird), "IRD", 3) < 0)
NOPE("Governance: Initial Reward Delay Parameter miss (IRD).");
if (ird == 0)
NOPE("Governance: Initial Reward Delay must be > 0.");
// set reward rate
ASSERT(state_set(SVAR(irr), "RR", 2));
etxn_reserve(imc);
// set reward delay
ASSERT(state_set(SVAR(ird), "RD", 2));
// set member count
ASSERT(state_set(SBUF(imc), "MC", 2));
// set reward rate
ASSERT(state_set(SVAR(irr), "RR", 2));
// set reward delay
ASSERT(state_set(SVAR(ird), "RD", 2));
distribution_amount = ida;
member_count = imc;
}
else if (is_distribution)
{
ASSERT(member_count > 0);
TRACEVAR(member_count);
uint8_t amt[8];
ASSERT(otxn_param(SBUF(amt), "reward", 5) == 8);
distribution_amount = INT64_FROM_BUF(amt);
distribution_amount /= member_count;
ASSERT(distribution_amount > 0);
}
if (is_setup || is_distribution)
{
for (uint8_t i = 0; GUARD(SEAT_COUNT), i < member_count; ++i)
{
uint8_t member_acc[20];
if (is_setup)
{
uint8_t member_pkey[3] = {'I', 'S', i};
if (hook_param(SBUF(member_acc), member_pkey, 3) != 20)
NOPE("Governance: One or more initial member account ID's is missing");
// 0... X where X is member id started from 1
// maps to the member's account ID
// reverse key
ASSERT(state_set(SBUF(member_acc), SVAR(i)) == 20);
// 0, 0... ACCOUNT ID maps to member_id (as above)
// forward key
ASSERT(state_set(SVAR(i), SBUF(member_acc)) == 1);
}
else if (state(SBUF(member_acc), SVAR(i)) != 20)
break;
// emit initial
uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE];
PREPARE_PAYMENT_SIMPLE(tx, distribution_amount, member_acc, 0, 0);
// emit the transaction
uint8_t emithash[32];
int64_t emit_result = emit(SBUF(emithash), SBUF(tx));
ASSERT(emit_result > 0);
if (DEBUG)
TRACEVAR(emit_result);
uint8_t member_pkey[3] = {'I', 'S', i};
if (hook_param(SBUF(member_acc), member_pkey, 3) != 20)
NOPE("Governance: One or more initial member account ID's is missing");
// 0... X where X is member id started from 1
// maps to the member's account ID
trace(SBUF("Member:"), SBUF(member_acc), 1);
// reverse key
ASSERT(state_set(SBUF(member_acc), SVAR(i)) == 20);
// 0, 0... ACCOUNT ID maps to member_id (as above)
// forward key
ASSERT(state_set(SVAR(i), SBUF(member_acc)) == 1);
}
DONE(is_setup
? "Governance: Setup completed successfully."
: "Governance: Reward distribution completed successfully.");
DONE("Governance: Setup completed successfully.");
}
// otherwise a normal execution (not initial)
// first let's check if the invoking party is a member
int64_t member_id = state(0,0,account_field + 12, 20);
if (member_id < 0)
NOPE("Governance: You are not currently a governance member.");
NOPE("Governance: You are not currently a governance member at this table.");
// the only thing a member can do is vote for a topic
// so lets process their vote
@@ -227,29 +205,32 @@ int64_t hook(uint32_t r)
// { 'S|H|R', '\0 + topicid' }
uint8_t topic[2];
int64_t result = otxn_param(SBUF(topic), "T", 1);
uint8_t t = topic[0]; // topic type
uint8_t n = topic[1]; // number (seats) (or R/D for reward rate/delay)
if (result != 2 || (
topic[0] != 'S' && // topic type: seat
topic[0] != 'H' && // topic type: hook
topic[0] != 'R')) // topic type: reward
t != 'S' && // topic type: seat (L1)
t != 'H' && // topic type: hook (L1)
t != 'R')) // topic type: reward
NOPE("Governance: Valid TOPIC must be specified as otxn parameter.");
if (topic[0] == 'S' && topic[1] > (SEAT_COUNT - 1))
if (t == 'S' && n > (SEAT_COUNT - 1))
NOPE("Governance: Valid seat topics are 0 through 19.");
if (topic[0] == 'H' && topic[1] > HOOK_MAX)
if (t == 'H' && n > HOOK_MAX)
NOPE("Governance: Valid hook topics are 0 through 9.");
if (topic[0] == 'R' && topic[1] != 'R' && topic[1] != 'D')
if (t == 'R' && n != 'R' && n != 'D')
NOPE("Governance: Valid reward topics are R (rate) and D (delay).");
// RH TODO: validate RR/RD xfl > 0
// RH TODO: validate RR/RD xfl > 0
uint8_t topic_data[32];
uint8_t topic_size =
topic[0] == 'H' ? 32 : // hook topics are a 32 byte hook hash
topic[0] == 'S' ? 20 : // account topics are a 20 byte account ID
8; // reward topics are an 8 byte le xfl
t == 'H' ? 32 : // hook topics are a 32 byte hook hash
t == 'S' ? 20 : // account topics are a 20 byte account ID
8; // reward topics are an 8 byte le xfl
uint8_t padding = 32 - topic_size;
@@ -260,12 +241,13 @@ int64_t hook(uint32_t r)
// reuse account_field to create vote key
account_field[0] = 'V';
account_field[1] = topic[0];
account_field[2] = topic[1];
account_field[1] = t;
account_field[2] = n;
// get their previous vote if any on this topic
uint8_t previous_topic_data[32];
int64_t previous_topic_size = state(previous_topic_data + padding, topic_size, SBUF(account_field));
int64_t previous_topic_size =
state(previous_topic_data + padding, topic_size, SBUF(account_field));
// check if the vote they're making has already been cast before,
// if it is identical to their existing vote for this topic then just end with tesSUCCESS
@@ -285,8 +267,8 @@ int64_t hook(uint32_t r)
uint8_t votes = 0;
// override the first two bytes to turn it into a vote count key
previous_topic_data[0] = 'C';
previous_topic_data[1] = topic[0];
previous_topic_data[2] = topic[1];
previous_topic_data[1] = t;
previous_topic_data[2] = n;
if (state(&votes, 1, SBUF(previous_topic_data)) && votes > 0)
{
@@ -303,8 +285,8 @@ int64_t hook(uint32_t r)
// so store the first bytes
uint64_t saved_data = *((uint64_t*)topic_data);
topic_data[0] = 'C';
topic_data[1] = topic[0];
topic_data[2] = topic[1];
topic_data[1] = t;
topic_data[2] = n;
state(&votes, 1, SBUF(topic_data));
votes++;
@@ -327,23 +309,27 @@ int64_t hook(uint32_t r)
trace(SBUF("topic"), topic, 2, 1);
}
// now check if we hit threshold
if (votes < (topic[0] == 'S' ? (member_count * 0.8) : member_count))
DONE("Governance: Vote recorded. Not yet enough votes to action.");
// 100%/80% threshold as needed is reached
int64_t q80 = member_count * 0.8;
if (votes <
t == 'S'
? q80 // L1s have 80% threshold for membership/seat voting
: member_count) // L1s have 100% threshold for all other voting
DONE("Governance: Vote recorded at L1. Not yet enough votes to action.");
// action vote
if (DEBUG)
TRACESTR("Actioning votes");
switch (topic[0])
switch (t)
{
case 'R':
{
// reward topics
ASSERT(state_set(topic_data, 8, SBUF(topic))); // interest rate is on the the FF, 0...0 key
if (topic[1] == 'R')
if (n == 'R')
DONE("Governance: Reward rate change actioned!");
DONE("Governance: Reward delay change actioned!");
@@ -355,14 +341,14 @@ int64_t hook(uint32_t r)
// first get the hook ledget object
uint8_t keylet[34];
util_keylet(SBUF(keylet), KEYLET_HOOK, SBUF(hook_accid), 0,0,0,0);
util_keylet(SBUF(keylet), KEYLET_HOOK, hook_accid + 12, 20, 0,0,0,0);
slot_set(SBUF(keylet), 5);
// now get the hooks array
slot_subfield(5, sfHooks, 6);
// now check the entry
if (slot_subarray(6, topic[1], 7) == 7)
if (slot_subarray(6, n, 7) == 7)
{
// it exists
// check if its identical
@@ -392,7 +378,7 @@ int64_t hook(uint32_t r)
uint8_t* h[10];
h[topic[1]] = hookhash;
h[n] = hookhash;
uint8_t emit_buf[1024];
uint32_t emit_size = 0;
@@ -414,7 +400,7 @@ int64_t hook(uint32_t r)
{
// add / change member
uint8_t previous_member[32];
int previous_present = (state(previous_member + 12, 20, topic[1], 1) == 20);
int previous_present = (state(previous_member + 12, 20, n, 1) == 20);
if (previous_present && !topic_data_zero)
{
@@ -478,10 +464,10 @@ int64_t hook(uint32_t r)
{
// add the new member
// reverse key
ASSERT(state_set(topic_data, 20, topic[1], 1) == 20);
ASSERT(state_set(topic_data, 20, n, 1) == 20);
// forward key
ASSERT(state_set(topic[1], 1, SBUF(topic_data)) == 20);
ASSERT(state_set(n, 1, SBUF(topic_data)) == 20);
}
DONE("Governance: Action member change.");

View File

@@ -1,4 +1,4 @@
all: reward govern
all: reward govern mint
reward:
wasmcc reward.c -o reward.wasm -Oz -Wl,--allow-undefined -I../
wasm-opt reward.wasm -o reward.wasm \
@@ -109,3 +109,58 @@ govern:
-Oz
hook-cleaner govern.wasm
guard_checker govern.wasm
mint:
wasmcc mint.c -o mint.wasm -Oz -Wl,--allow-undefined -I../
wasm-opt mint.wasm -o mint.wasm \
--shrink-level=100000000 \
--coalesce-locals-learning \
--vacuum \
--merge-blocks \
--merge-locals \
--flatten \
--ignore-implicit-traps \
-ffm \
--const-hoisting \
--code-folding \
--code-pushing \
--dae-optimizing \
--dce \
--simplify-globals-optimizing \
--simplify-locals-nonesting \
--reorder-locals \
--rereloop \
--precompute-propagate \
--local-cse \
--remove-unused-brs \
--memory-packing \
-c \
--avoid-reinterprets \
-Oz
hook-cleaner mint.wasm
wasm-opt mint.wasm -o mint.wasm \
--shrink-level=100000000 \
--coalesce-locals-learning \
--vacuum \
--merge-blocks \
--merge-locals \
--flatten \
--ignore-implicit-traps \
-ffm \
--const-hoisting \
--code-folding \
--code-pushing \
--dae-optimizing \
--dce \
--simplify-globals-optimizing \
--simplify-locals-nonesting \
--reorder-locals \
--rereloop \
--precompute-propagate \
--local-cse \
--remove-unused-brs \
--memory-packing \
-c \
--avoid-reinterprets \
-Oz
hook-cleaner mint.wasm
guard_checker mint.wasm

164
hook/mint.c Normal file
View File

@@ -0,0 +1,164 @@
// This hook just tests GenesisMint transactor, it is not for production use
#include "hookapi.h"
#define ASSERT(x)\
if (!(x))\
rollback(SBUF("Reward: Assertion failure."),__LINE__);
#define DEBUG 1
uint8_t txn_mint[13850] =
{
/* size,upto */
/* 3, 0 */ 0x12U, 0x00U, 0x60U, /* tt = GenesisMint */
/* 5, 3 */ 0x22U, 0x80U, 0x00U, 0x00U, 0x00U, /* flags = tfCanonical */
/* 5, 8 */ 0x24U, 0x00U, 0x00U, 0x00U, 0x00U, /* sequence = 0 */
/* 6, 13 */ 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U, /* first ledger seq */
/* 6, 19 */ 0x20U, 0x1BU, 0x00U, 0x00U, 0x00U, 0x00U, /* last ledger seq */
/* 9, 25 */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, /* fee */
/* 35, 34 */ 0x73U, 0x21U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* pubkey */
/* 22, 69 */ 0x81U, 0x14U, 0xB5U,0xF7U,0x62U,0x79U,0x8AU,0x53U,0xD5U,0x43U,0xA0U,0x14U,
0xCAU,0xF8U,0xB2U,0x97U,0xCFU,0xF8U,0xF2U,0xF9U,0x37U,0xE8U, /* src acc */
/* 116, 91 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* emit detail */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
/* 207, ... */ 0xF0U, 0x60U, /* gen mints arr */
/* 34 bytes per entries + 1 tail byte
E060
61
4111111111111111 // amount
8114
1234567891234567891234567891234567891234 // account
E1
... repeat
F1 // tail byte
*
* */
// 210 bytes + 34 bytes per entry * number of entries + any alignment padding desired
};
uint8_t entry[40] = {
/* 0, 2 */ 0xE0U, 0x60U, // obj start
/* 2, 1 */ 0x61U, // amount header
/* 3, 8 */ 0,0,0,0,0,0,0,0, // amount payload
/* 11, 2 */ 0x83U, 0x14U, // account header
/* 13, 20 */ 0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0, // account payload
/* 33, 1 */ 0xE1U // obj end
};
#define ENTRY_DROPS(drops_tmp)\
{\
uint8_t* b = entry + 3U;\
*b++ = 0b01000000 + (( drops_tmp >> 56 ) & 0b00111111 );\
*b++ = (drops_tmp >> 48) & 0xFFU;\
*b++ = (drops_tmp >> 40) & 0xFFU;\
*b++ = (drops_tmp >> 32) & 0xFFU;\
*b++ = (drops_tmp >> 24) & 0xFFU;\
*b++ = (drops_tmp >> 16) & 0xFFU;\
*b++ = (drops_tmp >> 8) & 0xFFU;\
*b++ = (drops_tmp >> 0) & 0xFFU;\
}
#define BE_DROPS(drops)\
{\
uint64_t drops_tmp = drops;\
uint8_t* b = (uint8_t*)&drops;\
*b++ = 0b01000000 + (( drops_tmp >> 56 ) & 0b00111111 );\
*b++ = (drops_tmp >> 48) & 0xFFU;\
*b++ = (drops_tmp >> 40) & 0xFFU;\
*b++ = (drops_tmp >> 32) & 0xFFU;\
*b++ = (drops_tmp >> 24) & 0xFFU;\
*b++ = (drops_tmp >> 16) & 0xFFU;\
*b++ = (drops_tmp >> 8) & 0xFFU;\
*b++ = (drops_tmp >> 0) & 0xFFU;\
}
//uint8_t preamble[4] = {0xE0U, 0x60U, 0x61U, 0x00};
int64_t hook(uint32_t r)
{
etxn_reserve(1);
_g(1,1);
// emit the txn
uint64_t drops = 12345;
ENTRY_DROPS(drops);
#define COUNT 400
uint8_t* upto = txn_mint + 209U;
uint8_t* end = upto + (34U * COUNT);
for (; GUARD(COUNT), upto < end; upto += 34)
{
uint64_t* d = (uint64_t*)upto;
uint64_t* s = (uint64_t*)entry;
*d++ = *s++;
*d++ = *s++;
//*d++ = *s++;
//*d++ = *s++;
*(d+2) = *(s+2);
otxn_field(upto + 13, 20, sfDestination);
*((uint32_t*)(upto + 13)) = upto;
/*
*
*upto++ = 0xE0U; // obj start
*upto++ = 0x60U;
*upto++ = 0x61U; // amt
*((uint32_t*)upto) = preamble;
upto += 3;
*((uint64_t*)upto) = drops;
upto += 8;
*upto++ = 0x83U; // acc
*upto++ = 0x14U;
*((uint16_t*)upto) = preamble2;
upto += 2;
*/
}
*upto++ = 0xF1U; // array end
etxn_details(txn_mint + 91, 116);
int64_t fee = etxn_fee_base(txn_mint, upto - txn_mint);
BE_DROPS(fee);
*((uint64_t*)(txn_mint + 26)) = fee;
int64_t seq = ledger_seq() + 1;
txn_mint[15] = (seq >> 24U) & 0xFFU;
txn_mint[16] = (seq >> 16U) & 0xFFU;
txn_mint[17] = (seq >> 8U) & 0xFFU;
txn_mint[18] = seq & 0xFFU;
seq += 4;
txn_mint[21] = (seq >> 24U) & 0xFFU;
txn_mint[22] = (seq >> 16U) & 0xFFU;
txn_mint[23] = (seq >> 8U) & 0xFFU;
txn_mint[24] = seq & 0xFFU;
trace(SBUF("emit:"), txn_mint, upto-txn_mint, 1);
uint8_t emithash[32];
int64_t emit_result = emit(SBUF(emithash), txn_mint, upto - txn_mint);
if (DEBUG)
TRACEVAR(emit_result);
if (emit_result < 0)
rollback(SBUF("MintTest: Emit failed."), __LINE__);
accept(SBUF("MintTest: Emitted txn successfully."), __LINE__);
}

View File

@@ -1,65 +1,100 @@
#include "hookapi.h"
//#define REWARD_DELAY 2600000LL
#define REWARD_DELAY 60LL
#define REWARD_DELAY 5LL
#define REWARD_MULTIPLIER_XFL 6038156834009797973ULL
//0.00333333333f
#define L1SEATS 20U
#define MAXUNL 128U
#define SVAR(x) &(x), sizeof(x)
#define ASSERT(x)\
if (!(x))\
rollback(SBUF("Reward: Assertion failure."),__LINE__);
#define DEBUG 1
// this txn template is sent back to this account to distribute a duplicate reward to governance members
// this distribution is handled by the governance reward hook
uint8_t txn_loopback[235] =
uint8_t txn_mint[928] =
{
/* size,upto */
/* 3, 0 */ 0x12U, 0x00U, 0x63U, /* tt = Invoke */
/* 3, 0 */ 0x12U, 0x00U, 0x60U, /* tt = GenesisMint */
/* 5, 3 */ 0x22U, 0x80U, 0x00U, 0x00U, 0x00U, /* flags = tfCanonical */
/* 5, 8 */ 0x24U, 0x00U, 0x00U, 0x00U, 0x00U, /* sequence = 0 */
/* 6, 13 */ 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U, /* first ledger seq */
/* 6, 19 */ 0x20U, 0x1BU, 0x00U, 0x00U, 0x00U, 0x00U, /* last ledger seq */
/* 9, 25 */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, /* fee */
/* 35, 34 */ 0x73U, 0x21U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* pubkey */
/* 22, 69 */ 0x81U, 0x14U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* src acc */
/* 22, 69 */ 0x81U, 0x14U, 0xB5U,0xF7U,0x62U,0x79U,0x8AU,0x53U,0xD5U,0x43U,0xA0U,0x14U,
0xCAU,0xF8U,0xB2U,0x97U,0xCFU,0xF8U,0xF2U,0xF9U,0x37U,0xE8U, /* src acc */
/* 116, 91 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* emit detail */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
/* 28,207 */ 0xF0U,0x13U,0xE0U,0x17U,0x70U,0x18U,0x06U,0x72U, /* hook params */
0x65U,0x77U,0x61U,0x72U,0x64U,0x70U,0x19U,0x08U, /* reward key */
/* 223 */ 0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U, /* value/drops */
0xE1U,0xF1U,0xE1U,0xF1 /* end params */
/* 235 */
/* 207, ... */ 0xF0U, 0x60U, /* gen mints arr */
/* 34 bytes per entries + 1 tail byte
E060
61
4111111111111111 // amount
8114
1234567891234567891234567891234567891234 // account
E1
... repeat
F1 // tail byte
*
* */
// 210 bytes + 34 bytes per entry * number of entries + any alignment padding desired
};
uint8_t template[40] = {
/* 0, 2 */ 0xE0U, 0x60U, // obj start
/* 2, 1 */ 0x61U, // amount header
/* 3, 8 */ 0,0,0,0,0,0,0,0, // amount payload
/* 11, 2 */ 0x83U, 0x14U, // account header
/* 13, 20 */ 0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0, // account payload
/* 33, 1 */ 0xE1U // obj end
};
// this txn template is used to pay out rewards
uint8_t txn_payment[238] =
#define TEMPLATE_DROPS(drops_tmp)\
{\
uint8_t* b = template + 3U;\
*b++ = 0b01000000 + (( drops_tmp >> 56 ) & 0b00111111 );\
*b++ = (drops_tmp >> 48) & 0xFFU;\
*b++ = (drops_tmp >> 40) & 0xFFU;\
*b++ = (drops_tmp >> 32) & 0xFFU;\
*b++ = (drops_tmp >> 24) & 0xFFU;\
*b++ = (drops_tmp >> 16) & 0xFFU;\
*b++ = (drops_tmp >> 8) & 0xFFU;\
*b++ = (drops_tmp >> 0) & 0xFFU;\
}
#define BE_DROPS(drops)\
{\
uint64_t drops_tmp = drops;\
uint8_t* b = (uint8_t*)&drops;\
*b++ = 0b01000000 + (( drops_tmp >> 56 ) & 0b00111111 );\
*b++ = (drops_tmp >> 48) & 0xFFU;\
*b++ = (drops_tmp >> 40) & 0xFFU;\
*b++ = (drops_tmp >> 32) & 0xFFU;\
*b++ = (drops_tmp >> 24) & 0xFFU;\
*b++ = (drops_tmp >> 16) & 0xFFU;\
*b++ = (drops_tmp >> 8) & 0xFFU;\
*b++ = (drops_tmp >> 0) & 0xFFU;\
}
uint8_t member_count_key[2] = {'M', 'C'};
uint8_t unlreport_keylet[34] =
{
/* size,upto */
/* 3, 0 */ 0x12U, 0x00U, 0x00U, /* tt = Payment */
/* 5, 3*/ 0x22U, 0x80U, 0x00U, 0x00U, 0x00U, /* flags = tfCanonical */
/* 5, 8 */ 0x24U, 0x00U, 0x00U, 0x00U, 0x00U, /* sequence = 0 */
/* 6, 13 */ 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U, /* first ledger seq */
/* 6, 19 */ 0x20U, 0x1BU, 0x00U, 0x00U, 0x00U, 0x00U, /* last ledger seq */
/* 9, 25 */ 0x61U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* amount field 9 bytes */
/* 9, 34 */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, /* fee */
/* 35, 43 */ 0x73U, 0x21U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* pubkey */
/* 22, 78 */ 0x81U, 0x14U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* src acc */
/* 22,100 */ 0x83U, 0x14U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* dst acc */
/* 116,122 */ /* emit details */
/* 0,238 */
0,0,0x61U,0xE3U,0x2EU,0x7AU,0x24U,0xA2U,0x38U,0xF1U,0xC6U,0x19U,
0xD5U,0xF9U,0xDDU,0xCCU,0x41U,0xA9U,0x4BU,0x33U,0xB6U,0x6CU,
0x01U,0x63U,0xF7U,0xEFU,0xCCU,0x8AU,0x19U,0xC9U,0xFDU,0x6FU,
0x28U,0xDCU
};
#define HOOK_ACCOUNT (txn_loopback + 71U)
#define HOOK_ACCOUNT_2 (txn_payment + 80U)
#define ACC_FIELD (txn_payment + 102U)
#define AMOUNT_OUT (txn_payment + 26U)
#define AMOUNT_OUT_2 (txn_loopback + 223U)
uint8_t msg_buf[] = "You must wait 0000000 seconds";
int64_t hook(uint32_t r)
{
@@ -70,21 +105,21 @@ int64_t hook(uint32_t r)
if (otxn_type() != 98)
accept(SBUF("Reward: Passing non-claim txn"), __LINE__);
uint8_t otxn_acc[20];
uint8_t hook_acc[20];
// get the account id
otxn_field(ACC_FIELD, 20, sfAccount);
otxn_field(SBUF(otxn_acc), sfAccount);
// write the hook account into the txn template
hook_account(HOOK_ACCOUNT, 20);
hook_account(SBUF(hook_acc));
// there are two txn templates so also write into the second template
hook_account(HOOK_ACCOUNT_2, 20);
if (BUFFER_EQUAL_20(HOOK_ACCOUNT, ACC_FIELD))
if (BUFFER_EQUAL_20(hook_acc, otxn_acc))
accept(SBUF("Reward: Passing outgoing txn"), __LINE__);
// get the account root keylet
uint8_t kl[34];
util_keylet(SBUF(kl), KEYLET_ACCOUNT, ACC_FIELD, 20, 0,0,0,0);
util_keylet(SBUF(kl), KEYLET_ACCOUNT, SBUF(otxn_acc), 0,0,0,0);
// slot the account root, this can't fail
slot_set(SBUF(kl), 1);
@@ -184,39 +219,118 @@ int64_t hook(uint32_t r)
TRACEVAR(xfl_reward);
// write drops to paymen txn
uint64_t reward_drops = float_int(xfl_reward, 6, 1);
uint64_t l1_drops = reward_drops / L1SEATS;
otxn_slot(1);
slot_subfield(1, sfFee, 2);
int64_t xfl_fee = slot_float(2);
// user gets back the fee they spent running the hook
if (xfl_fee > 0)
reward_drops += float_int(xfl_fee, 6, 1);
TEMPLATE_DROPS(reward_drops);
uint8_t* upto = txn_mint + 209U;
uint8_t* end = upto + (34U * (L1SEATS + 1));
// first account is always the rewardee
{
uint64_t drops = float_int(xfl_reward, 6, 1);
uint8_t* b = AMOUNT_OUT;
*b++ = 0b01000000 + (( drops >> 56 ) & 0b00111111 );
*b++ = (drops >> 48) & 0xFFU;
*b++ = (drops >> 40) & 0xFFU;
*b++ = (drops >> 32) & 0xFFU;
*b++ = (drops >> 24) & 0xFFU;
*b++ = (drops >> 16) & 0xFFU;
*b++ = (drops >> 8) & 0xFFU;
*b++ = (drops >> 0) & 0xFFU;
// copy to other template
*((uint64_t*)AMOUNT_OUT_2) = *((uint64_t*)AMOUNT_OUT);
uint64_t* d = (uint64_t*)upto;
uint64_t* s = (uint64_t*)template;
*d++ = *s++;
*d++ = *s++;
*(d+2) = *(s+2);
otxn_field(upto + 13, 20, sfAccount);
}
upto += 34U;
// now iterate all possible seats in all possible tables
TEMPLATE_DROPS(l1_drops);
// there are two conditions for L1 governance members to receive rewards:
// 1. they must be an L1 member
// 2. they must be an active validator
// load the UNLReport, this will let us know who has been validating and who hasn't
uint64_t can_reward[L1SEATS];
uint8_t av_array[(60 * MAXUNL) + 4];
if (slot_set(SBUF(unlreport_keylet), 1) == 1 &&
slot_subfield(1, sfActiveValidators, 1) == 1 &&
slot(SBUF(av_array), 1) > 0)
{
// at least some validators have been validating so those get a reward if they are on the governance table
// we are going to assume the UNL never exceeds 64
uint8_t seat = 0;
uint8_t* av_upto = av_array + 39 /* offset to the first key */;
uint64_t av_size = slot_count(1);
if (av_size > MAXUNL) av_size = MAXUNL;
for (uint64_t i = 0; GUARD(MAXUNL), i < av_size; ++i, av_upto += 60U)
{
trace(SBUF("av:"), av_upto, 20, 1);
if (state(SVAR(seat), av_upto, 20) != 1 || seat > L1SEATS)
continue;
can_reward[seat] = 1;
}
// iterate the seats at the table and add reward entries for the active validators
for (uint8_t l1_seat = 0; upto < end && l1_seat < L1SEATS; l1_seat++)
{
GUARD(L1SEATS);
if (!can_reward[l1_seat])
continue;
// copy template 1 into next GenesisMints array position
uint64_t* d = (uint64_t*)upto;
uint64_t* s = (uint64_t*)template;
*d++ = *s++;
*d++ = *s++;
*(d+2) = *(s+2);
if (state(upto + 13, 20, &l1_seat, 1) == 20)
upto += 34;
}
}
*upto++ = 0xF1U;
// populate other txn fields
etxn_details(txn_mint + 91, 116);
int64_t fee = etxn_fee_base(txn_mint, upto - txn_mint);
BE_DROPS(fee);
*((uint64_t*)(txn_mint + 26)) = fee;
int64_t seq = ledger_seq() + 1;
txn_mint[15] = (seq >> 24U) & 0xFFU;
txn_mint[16] = (seq >> 16U) & 0xFFU;
txn_mint[17] = (seq >> 8U) & 0xFFU;
txn_mint[18] = seq & 0xFFU;
seq += 4;
txn_mint[21] = (seq >> 24U) & 0xFFU;
txn_mint[22] = (seq >> 16U) & 0xFFU;
txn_mint[23] = (seq >> 8U) & 0xFFU;
txn_mint[24] = seq & 0xFFU;
trace(SBUF("emit:"), txn_mint, upto-txn_mint, 1);
// emit the payment transaction
uint8_t emithash[32];
int64_t emit_result = emit(SBUF(emithash), SBUF(txn_payment));
int64_t emit_result = emit(SBUF(emithash), txn_mint, upto - txn_mint);
if (DEBUG)
TRACEVAR(emit_result);
if (emit_result < 0)
rollback(SBUF("Reward: Emit reward failed."), __LINE__);
// emit the loopback txn
emit_result = emit(SBUF(emithash), SBUF(txn_loopback));
if (DEBUG)
TRACEVAR(emit_result);
if (emit_result < 0)
rollback(SBUF("Reward: Emit loopback failed."), __LINE__);

View File

@@ -211,4 +211,5 @@
#define sfDisabledValidators ((15U << 16U) + 17U)
#define sfHookExecutions ((15U << 16U) + 18U)
#define sfHookParameters ((15U << 16U) + 19U)
#define sfHookGrants ((15U << 16U) + 20U)
#define sfHookGrants ((15U << 16U) + 20U)
#define sfActiveValidators ((15U << 16U) + 95U)

View File

@@ -92,7 +92,7 @@ RCLConsensus::Adaptor::Adaptor(
rand_int(
crypto_prng(),
std::numeric_limits<std::uint64_t>::max() - 1))
, nUnlVote_(validatorKeys_.nodeID, j_)
, nUnlVote_(validatorKeys_.nodeID, j_, app)
{
assert(valCookie_ != 0);

View File

@@ -416,7 +416,7 @@ private:
/** A ledger wrapped in a CachedView. */
using CachedLedger = CachedView<Ledger>;
std::uint32_t constexpr FLAG_LEDGER_INTERVAL = 256;
std::uint32_t constexpr FLAG_LEDGER_INTERVAL = 16;
/** Returns true if the given ledgerIndex is a flag ledgerIndex */
bool
isFlagLedger(LedgerIndex seq);

View File

@@ -1327,8 +1327,11 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline)
if (config_->IMPORT_VL_KEYS.empty())
{
JLOG(m_journal.fatal()) << "IMPORT_VL_KEYS section must be specified in validators file.";
return false;
JLOG(m_journal.warn()) << "[import_vl_keys] section not specified validators file. "
<< "Will attempt to use keys from ledger.";
}
else
{
}
//----------------------------------------------------------------------

View File

@@ -23,8 +23,8 @@
namespace ripple {
NegativeUNLVote::NegativeUNLVote(NodeID const& myId, beast::Journal j)
: myId_(myId), j_(j)
NegativeUNLVote::NegativeUNLVote(NodeID const& myId, beast::Journal j, Application& app)
: myId_(myId), j_(j), app_(app)
{
}
@@ -99,9 +99,98 @@ NegativeUNLVote::doVoting(
assert(nidToKeyMap.count(n));
addTx(seq, nidToKeyMap[n], ToReEnable, initialSet);
}
// do reporting when enabled
if (prevLedger->rules().enabled(featureXahauGenesis) && scoreTable->size() > 0)
addReportingTx(seq, *scoreTable, nidToKeyMap, initialSet);
}
}
void
NegativeUNLVote::addReportingTx(
LedgerIndex seq,
hash_map<NodeID, std::uint32_t> const& scoreTable,
hash_map<NodeID, PublicKey> const& nidToKeyMap,
std::shared_ptr<SHAMap> const& initalSet)
{
// RH NOTE: now that we use one key per txn with lots of txns
// this ordering step is probably not needed
std::set<PublicKey> ordered;
for (auto const& [n, score]: scoreTable)
{
if (score > (FLAG_LEDGER_INTERVAL>>1))
ordered.emplace(nidToKeyMap.at(n));
}
for (auto const& pk : ordered)
{
STTx repUnlTx(ttUNL_REPORT, [&](auto& obj)
{
obj.set(([&]()
{
auto inner = std::make_unique<STObject>(sfActiveValidator);
inner->setFieldVL(sfPublicKey, pk);
return inner;
})());
obj.setFieldU32(sfLedgerSequence, seq);
});
uint256 txID = repUnlTx.getTransactionID();
Serializer s;
repUnlTx.add(s);
if (!initalSet->addGiveItem(
SHAMapNodeType::tnTRANSACTION_NM,
std::make_shared<SHAMapItem>(txID, s.slice())))
{
JLOG(j_.warn()) << "R-UNL: ledger seq=" << seq
<< ", add ttUNL_REPORT tx failed";
}
else
{
JLOG(j_.debug()) << "R-UNL: ledger seq=" << seq
<< ", add a ttUNL_REPORT (active_val) Tx with txID: " << txID
<< ", size=" << s.size()
<< ", " << repUnlTx.getJson(JsonOptions::none);
}
}
// do import VL key voting
auto const& keyMap = app_.config().IMPORT_VL_KEYS;
for (auto const& [_, pk] : keyMap)
{
STTx repUnlTx(ttUNL_REPORT, [&](auto& obj)
{
obj.set(([&]()
{
auto inner = std::make_unique<STObject>(sfImportVLKey);
inner->setFieldVL(sfPublicKey, pk);
return inner;
})());
obj.setFieldU32(sfLedgerSequence, seq);
});
uint256 txID = repUnlTx.getTransactionID();
Serializer s;
repUnlTx.add(s);
if (!initalSet->addGiveItem(
SHAMapNodeType::tnTRANSACTION_NM,
std::make_shared<SHAMapItem>(txID, s.slice())))
{
JLOG(j_.warn()) << "R-UNL: ledger seq=" << seq
<< ", add ttUNL_REPORT tx failed (import_vl_key)";
}
else
{
JLOG(j_.debug()) << "R-UNL: ledger seq=" << seq
<< ", add a ttUNL_REPORT (import_vl) Tx with txID: " << txID
<< ", size=" << s.size()
<< ", " << repUnlTx.getJson(JsonOptions::none);
}
}
}
void
NegativeUNLVote::addTx(
LedgerIndex seq,

View File

@@ -91,7 +91,7 @@ public:
* @param myId the NodeID of the local node
* @param j log
*/
NegativeUNLVote(NodeID const& myId, beast::Journal j);
NegativeUNLVote(NodeID const& myId, beast::Journal j, Application& app);
~NegativeUNLVote() = default;
/**
@@ -128,6 +128,7 @@ private:
beast::Journal j_;
mutable std::mutex mutex_;
hash_map<NodeID, LedgerIndex> newValidators_;
Application& app_;
/**
* UNLModify Tx candidates
@@ -153,6 +154,16 @@ private:
NegativeUNLModify modify,
std::shared_ptr<SHAMap> const& initialSet);
/**
* As above, but make a report object instead of an n-unl
*/
void
addReportingTx(
LedgerIndex seq,
hash_map<NodeID, std::uint32_t> const& scoreTable,
hash_map<NodeID, PublicKey> const& nidToKeyMap,
std::shared_ptr<SHAMap> const& initalSet);
/**
* Pick one candidate from a vector of candidates.
*

View File

@@ -79,9 +79,6 @@ namespace ripple {
class NetworkOPsImp final : public NetworkOPs
{
// ledger_seq -> validator key -> validation message
std::map<uint32_t, std::map<PublicKey, STValidation>> xpop_history;
/**
* Transaction with input flags and results to be applied in batches.
*/
@@ -2341,37 +2338,6 @@ NetworkOPsImp::recvValidation(
handleNewValidation(app_, val, source);
// manage xpop validation history
if (app_.config().XPOP_HISTORY)
{
if (val->isTrusted())
{
uint32_t seq = val->getFieldU32(sfLedgerSequence);
if (xpop_history.find(seq) == xpop_history.end())
xpop_history.emplace(seq, std::map<PublicKey, STValidation>{});
xpop_history[seq].emplace(val->getSignerPublic(), *val);
}
uint32_t delete_threshold =
app_.getLedgerMaster().getValidLedgerIndex() + 1 - *(app_.config().XPOP_HISTORY);
if (xpop_history.find(delete_threshold) != xpop_history.end())
xpop_history.erase(delete_threshold);
if (xpop_history.size() > *(app_.config().XPOP_HISTORY))
{
std::unordered_set<uint32_t> to_delete;
for (auto const& kp : xpop_history)
if (kp.first < delete_threshold)
to_delete.emplace(kp.first);
for (uint32_t td: to_delete)
xpop_history.erase(td);
}
}
pubValidation(val);
// We will always relay trusted validations; if configured, we will

View File

@@ -28,6 +28,9 @@
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/TxFlags.h>
#include <string_view>
#include <ripple/app/hook/Guard.h>
#include <ripple/protocol/AccountID.h>
#include <ripple/app/hook/applyHook.h>
namespace ripple {
@@ -74,6 +77,21 @@ Change::preflight(PreflightContext const& ctx)
return temDISABLED;
}
if (ctx.tx.getTxnType() == ttUNL_REPORT)
{
if (!ctx.rules.enabled(featureXahauGenesis))
{
JLOG(ctx.j.warn()) << "Change: UNLReport is not enabled.";
return temDISABLED;
}
if (!ctx.tx.isFieldPresent(sfActiveValidator) && !ctx.tx.isFieldPresent(sfImportVLKey))
{
JLOG(ctx.j.warn()) << "Change: UNLReport must specify at least one of sfImportVLKey, sfActiveValidator";
return temMALFORMED;
}
}
return tesSUCCESS;
}
@@ -131,6 +149,7 @@ Change::preclaim(PreclaimContext const& ctx)
return tesSUCCESS;
case ttAMENDMENT:
case ttUNL_MODIFY:
case ttUNL_REPORT:
case ttEMIT_FAILURE:
return tesSUCCESS;
default:
@@ -151,18 +170,330 @@ Change::doApply()
return applyUNLModify();
case ttEMIT_FAILURE:
return applyEmitFailure();
case ttUNL_REPORT:
return applyUNLReport();
default:
assert(0);
return tefFAILURE;
}
}
TER
Change::applyUNLReport()
{
auto sle = view().peek(keylet::UNLReport());
auto const seq = view().info().seq;
bool const created = !sle;
if (created)
sle = std::make_shared<SLE>(keylet::UNLReport());
bool const reset =
sle->isFieldPresent(sfPreviousTxnLgrSeq) &&
sle->getFieldU32(sfPreviousTxnLgrSeq) < seq;
auto canonicalize = [&](SField const& arrayType, SField const& objType)
-> std::vector<STObject>
{
auto const existing =
reset || !sle->isFieldPresent(arrayType)
? STArray(arrayType)
: sle->getFieldArray(arrayType);
// canonically order using std::set
std::map<PublicKey, AccountID> ordered;
for (auto const& obj: existing)
{
auto pk = obj.getFieldVL(sfPublicKey);
if (!publicKeyType(makeSlice(pk)))
continue;
PublicKey p(makeSlice(pk));
ordered.emplace(p,
obj.isFieldPresent(sfAccount) ? obj.getAccountID(sfAccount) : calcAccountID(p));
};
if (ctx_.tx.isFieldPresent(objType))
{
auto pk =
const_cast<ripple::STTx&>(ctx_.tx)
.getField(objType)
.downcast<STObject>()
.getFieldVL(sfPublicKey);
if (publicKeyType(makeSlice(pk)))
{
PublicKey p(makeSlice(pk));
ordered.emplace(p, calcAccountID(p));
}
}
std::vector<STObject> out;
out.reserve(ordered.size());
for (auto const& [k, a]: ordered)
{
out.emplace_back(objType);
out.back().setFieldVL(sfPublicKey, k);
out.back().setAccountID(sfAccount, a);
}
return out;
};
bool const hasAV = ctx_.tx.isFieldPresent(sfActiveValidator);
bool const hasVL = ctx_.tx.isFieldPresent(sfImportVLKey);
// update
if (hasAV)
sle->setFieldArray(sfActiveValidators,
STArray(canonicalize(sfActiveValidators, sfActiveValidator),sfActiveValidators));
if (hasVL)
sle->setFieldArray(sfImportVLKeys,
STArray(canonicalize(sfImportVLKeys, sfImportVLKey),sfImportVLKeys));
if (created)
view().insert(sle);
else
view().update(sle);
return tesSUCCESS;
}
void
Change::preCompute()
{
assert(account_ == beast::zero);
}
void
Change::activateXahauGenesis()
{
return;
JLOG(j_.warn()) << "featureXahauGenesis amendment activation code starting";
constexpr XRPAmount GENESIS { 1'000'000 * DROPS_PER_XRP };
constexpr XRPAmount INFRA { 10'000'000 * DROPS_PER_XRP};
constexpr XRPAmount EXCHANGE { 2'000'000 * DROPS_PER_XRP};
const static std::vector<std::pair<std::string, XRPAmount>>
initial_distribution =
{
{"rMYm3TY5D3rXYVAz6Zr2PDqEcjsTYbNiAT", INFRA},
};
const static std::vector<std::pair<uint256, std::vector<uint8_t>>>
genesis_hooks =
{
{ ripple::uint256("0000000000000000000000000000000000000000000000000000000000000001"),
{0x0}
},
};
Sandbox sb(&view());
// Step 1: mint genesis distribution
for (auto const& [account, amount] : initial_distribution)
{
auto accid_raw = parseBase58<AccountID>(account);
if (!accid_raw)
{
JLOG(j_.warn())
<< "featureXahauGenesis could not parse an r-address: " << account << ", bailing.";
return;
}
auto accid = *accid_raw;
auto const kl = keylet::account(accid);
auto sle = sb.peek(kl);
auto const exists = !!sle;
STAmount bal = exists ? sle->getFieldAmount(sfBalance) + STAmount{amount} : STAmount{amount};
if (bal <= beast::zero)
{
JLOG(j_.warn())
<< "featureXahauGenesis tried to set <= 0 balance on " << account << ", bailing";
return;
}
// the account should not exist but if it does then handle it properly
if (!exists)
{
sle = std::make_shared<SLE>(kl);
sle->setAccountID(sfAccount, accid);
std::uint32_t const seqno{
sb.rules().enabled(featureDeletableAccounts) ? sb.seq()
: 1};
sle->setFieldU32(sfSequence, seqno);
}
sle->setFieldAmount(sfBalance, bal);
if (exists)
sb.update(sle);
else
sb.insert(sle);
};
// Step 2: burn genesis funds to (almost) zero
static auto const accid = calcAccountID(
generateKeyPair(KeyType::secp256k1, generateSeed("masterpassphrase"))
.first);
auto const kl = keylet::account(accid);
auto sle = sb.peek(kl);
if (!sle)
{
JLOG(j_.warn())
<< "featureXahauGenesis genesis account doesn't exist!!";
return;
}
sle->setFieldAmount(sfBalance, GENESIS);
// Step 3: blackhole genesis
sle->setAccountID(sfRegularKey, noAccount());
sle->setFieldU32(sfFlags, lsfDisableMaster);
// Step 4: install genesis hooks
sle->setFieldU32(sfOwnerCount, sle->getFieldU32(sfOwnerCount) + genesis_hooks.size());
sb.update(sle);
if (sb.exists(keylet::hook(accid)))
{
JLOG(j_.warn())
<< "featureXahauGenesis genesis account already has hooks object in ledger, bailing";
return;
}
{
ripple::STArray hooks{sfHooks, genesis_hooks.size()};
int hookCount = 0;
for (auto const& [hookOn, wasmBytes] : genesis_hooks)
{
std::ostringstream loggerStream;
auto result =
validateGuards(
wasmBytes, // wasm to verify
loggerStream,
"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"
);
if (!result)
{
std::string s = loggerStream.str();
char* data = s.data();
size_t len = s.size();
char* last = data;
size_t i = 0;
for (; i < len; ++i)
{
if (data[i] == '\n')
{
data[i] = '\0';
j_.warn() << last;
last = data + i;
}
}
if (last < data + i)
j_.warn() << last;
JLOG(j_.warn())
<< "featureXahauGenesis initial hook failed to validate guards, bailing";
return;
}
std::optional<std::string> result2 =
hook::HookExecutor::validateWasm(wasmBytes.data(), (size_t)wasmBytes.size());
if (result2)
{
JLOG(j_.warn())
<< "featureXahauGenesis tried to set a hook with invalid code. VM error: "
<< *result2 << ", bailing";
return;
}
auto hookHash = ripple::sha512Half_s(ripple::Slice(wasmBytes.data(), wasmBytes.size()));
auto const kl = keylet::hookDefinition(hookHash);
if (view().exists(kl))
{
JLOG(j_.warn())
<< "featureXahauGenesis genesis hookDefinition already exists !!! bailing";
return;
}
auto hookDef = std::make_shared<SLE>(kl);
hookDef->setFieldH256(sfHookHash, hookHash);
hookDef->setFieldH256(sfHookOn, hookOn);
hookDef->setFieldH256(sfHookNamespace, UINT256_BIT[hookCount++]);
hookDef->setFieldArray(sfHookParameters, STArray{});
hookDef->setFieldU8(sfHookApiVersion, 0);
hookDef->setFieldVL(sfCreateCode, wasmBytes);
hookDef->setFieldH256(sfHookSetTxnID, ctx_.tx.getTransactionID());
hookDef->setFieldU64(sfReferenceCount, 1);
hookDef->setFieldAmount(sfFee,
XRPAmount {hook::computeExecutionFee(result->first)});
if (result->second > 0)
hookDef->setFieldAmount(sfHookCallbackFee,
XRPAmount {hook::computeExecutionFee(result->second)});
sb.insert(hookDef);
STObject hookObj {sfHook};
hookObj.setFieldH256(sfHookHash, hookHash);
hooks.push_back(hookObj);
}
auto sle = std::make_shared<SLE>(keylet::hook(accid));
sle->setFieldArray(sfHooks, hooks);
sle->setAccountID(sfAccount, accid);
auto const page = sb.dirInsert(
keylet::ownerDir(accid),
keylet::hook(accid),
describeOwnerDir(accid));
if (!page)
{
JLOG(j_.warn())
<< "featureXahauGenesis genesis directory full when trying to insert hooks object, bailing";
return;
}
sle->setFieldU64(sfOwnerNode, *page);
sb.insert(sle);
}
JLOG(j_.warn()) << "featureXahauGenesis amendment executed successfully";
sb.apply(ctx_.rawView());
}
void
Change::activateTrustLinesToSelfFix()
{
@@ -323,6 +654,8 @@ Change::applyAmendment()
if (amendment == fixTrustLinesToSelf)
activateTrustLinesToSelfFix();
else if (amendment == featureXahauGenesis)
activateXahauGenesis();
ctx_.app.getAmendmentTable().enable(amendment);

View File

@@ -59,6 +59,9 @@ private:
void
activateTrustLinesToSelfFix();
void
activateXahauGenesis();
TER
applyAmendment();
@@ -70,6 +73,9 @@ private:
TER
applyEmitFailure();
TER
applyUNLReport();
};
} // namespace ripple

View File

@@ -0,0 +1,221 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/tx/impl/GenesisMint.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
namespace ripple {
TxConsequences
GenesisMint::makeTxConsequences(PreflightContext const& ctx)
{
// RH TODO: review this
return TxConsequences{ctx.tx, TxConsequences::normal};
}
NotTEC
GenesisMint::preflight(PreflightContext const& ctx)
{
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;
auto& tx = ctx.tx;
auto const id = ctx.tx[sfAccount];
static auto const genesisAccountId = calcAccountID(
generateKeyPair(KeyType::secp256k1, generateSeed("masterpassphrase"))
.first);
if (id != genesisAccountId || !tx.isFieldPresent(sfEmitDetails))
{
JLOG(ctx.j.warn())
<< "GenesisMint: can only be used by the genesis account in an emitted transaction.";
return temMALFORMED;
}
auto const& dests = tx.getFieldArray(sfGenesisMints);
if (dests.empty())
{
JLOG(ctx.j.warn())
<< "GenesisMint: destinations array empty.";
return temMALFORMED;
}
if (dests.size() > 512)
{
JLOG(ctx.j.warn())
<< "GenesisMint: destinations array exceeds 512 entries.";
return temMALFORMED;
}
std::unordered_set<AccountID> alreadySeen;
for (auto const& dest: dests)
{
if (dest.getFName() != sfGenesisMint)
{
JLOG(ctx.j.warn())
<< "GenesisMint: destinations array contained an invalid entry.";
return temMALFORMED;
}
bool const hasAmt = dest.isFieldPresent(sfAmount);
bool const hasMarks = dest.isFieldPresent(sfGovernanceMarks);
bool const hasFlags = dest.isFieldPresent(sfGovernanceFlags);
if (!hasAmt && !hasMarks && !hasFlags)
{
JLOG(ctx.j.warn())
<< "GenesisMint: each destination must have at least one of: "
<< "sfAmount, sfGovernanceFlags, sfGovernance marks.";
return temMALFORMED;
}
if (hasAmt)
{
auto const amt = dest.getFieldAmount(sfAmount);
if (!isXRP(amt))
{
JLOG(ctx.j.warn())
<< "GenesisMint: only native amounts can be minted.";
return temMALFORMED;
}
if (amt <= beast::zero)
{
JLOG(ctx.j.warn())
<< "GenesisMint: only positive amounts can be minted.";
return temMALFORMED;
}
}
auto const accid = dest.getAccountID(sfDestination);
if (accid == noAccount() || accid == xrpAccount())
{
JLOG(ctx.j.warn())
<< "GenesisMint: destinations includes disallowed account zero or one.";
return temMALFORMED;
}
if (alreadySeen.find(accid) != alreadySeen.end())
{
JLOG(ctx.j.warn())
<< "GenesisMint: duplicate in destinations.";
return temMALFORMED;
}
alreadySeen.emplace(accid);
}
return preflight2(ctx);
}
TER
GenesisMint::preclaim(PreclaimContext const& ctx)
{
if (!ctx.view.rules().enabled(featureHooks))
return temDISABLED;
if (!ctx.view.rules().enabled(featureXahauGenesis))
return temDISABLED;
// RH UPTO:
// check that printing won't exceed 200% of the total coins on the ledger
// this will act as a hard cap against malfunction
// modify the invariant checkers
return tesSUCCESS;
}
TER
GenesisMint::doApply()
{
auto const& dests = ctx_.tx.getFieldArray(sfGenesisMints);
for (auto const& dest: dests)
{
auto const amt = dest[~sfAmount];
auto const flags = dest[~sfGovernanceFlags];
auto const marks = dest[~sfGovernanceMarks];
auto const id = dest.getAccountID(sfDestination);
auto const k = keylet::account(id);
auto sle = view().peek(k);
bool const created = !sle;
if (created)
{
// Create the account.
std::uint32_t const seqno{
view().rules().enabled(featureDeletableAccounts) ? view().seq()
: 1};
sle = std::make_shared<SLE>(k);
sle->setAccountID(sfAccount, id);
sle->setFieldU32(sfSequence, seqno);
if (amt)
sle->setFieldAmount(sfBalance, *amt);
else // give them 2 XRP if the account didn't exist, same as ttIMPORT
sle->setFieldAmount(sfBalance, XRPAmount {2 * DROPS_PER_XRP});
}
else if (amt)
{
// Credit the account
STAmount startBal = sle->getFieldAmount(sfBalance);
STAmount finalBal = startBal + *amt;
if (finalBal > startBal)
sle->setFieldAmount(sfBalance, finalBal);
else
{
JLOG(ctx_.journal.warn())
<< "GenesisMint: cannot credit " << dest << " due to balance overflow";
}
}
// set flags and marks as applicable
if (flags)
sle->setFieldH256(sfGovernanceFlags, *flags);
if (marks)
sle->setFieldH256(sfGovernanceMarks, *marks);
if (created)
view().insert(sle);
else
view().update(sle);
}
return tesSUCCESS;
}
XRPAmount
GenesisMint::calculateBaseFee(ReadView const& view, STTx const& tx)
{
return XRPAmount { 0 } ;
}
} // namespace ripple

View File

@@ -0,0 +1,57 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_TX_GENESISMINT_H_INCLUDED
#define RIPPLE_TX_GENESISMINT_H_INCLUDED
#include <ripple/app/tx/impl/Transactor.h>
#include <ripple/basics/Log.h>
#include <ripple/core/Config.h>
#include <ripple/protocol/Indexes.h>
namespace ripple {
class GenesisMint : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
explicit GenesisMint(ApplyContext& ctx) : Transactor(ctx)
{
}
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace ripple
#endif

View File

@@ -163,11 +163,28 @@ Import::preflight(PreflightContext const& ctx)
if (!xpop)
return temMALFORMED;
// check if we recognise the vl key
auto const& vlKeys = ctx.app.config().IMPORT_VL_KEYS;
auto const found = std::ranges::find(vlKeys, (*xpop)[jss::validation][jss::unl][jss::public_key].asString());
if (found == vlKeys.end())
return telIMPORT_VL_KEY_NOT_RECOGNISED;
// we will check if we recognise the vl key in preclaim because it may be from on-ledger object
std::optional<PublicKey> masterVLKey;
{
std::string strPk = (*xpop)[jss::validation][jss::unl][jss::public_key].asString();
auto pkHex = strUnHex(strPk);
if (!pkHex)
{
JLOG(ctx.j.warn())
<< "Import: validation.unl.public_key was not valid hex.";
return temMALFORMED;
}
auto const pkType = publicKeyType(makeSlice(*pkHex));
if (!pkType)
{
JLOG(ctx.j.warn())
<< "Import: validation.unl.public_key was not a recognised public key type.";
return temMALFORMED;
}
masterVLKey = PublicKey(makeSlice(*pkHex));
}
auto const [stpTrans, meta] = getInnerTxn(tx, ctx.j, &(*xpop));
@@ -337,7 +354,9 @@ Import::preflight(PreflightContext const& ctx)
return temMALFORMED;
}
if (strHex(m->masterKey) != *found)
// we will check the master key matches a known one in preclaim, because the import vl key might be
// from the on-ledger object
if (m->masterKey != masterVLKey)
{
JLOG(ctx.j.warn())
<< "Import: manifest master key did not match top level master key in unl section of xpop "
@@ -352,6 +371,7 @@ Import::preflight(PreflightContext const& ctx)
<< tx.getTransactionID();
return temMALFORMED;
}
// manifest signing (ephemeral) key
auto const signingKey = m->signingKey;
@@ -876,7 +896,34 @@ Import::preclaim(PreclaimContext const& ctx)
if (sleVL && sleVL->getFieldU32(sfImportSequence) > vlInfo->first)
return tefPAST_IMPORT_VL_SEQ;
return tesSUCCESS;
// check master VL key
std::string strPk = (*xpop)[jss::validation][jss::unl][jss::public_key].asString();
if (auto const& found = ctx.app.config().IMPORT_VL_KEYS.find(strPk);
found != ctx.app.config().IMPORT_VL_KEYS.end())
return tesSUCCESS;
// not found in our local VL keys
auto pkHex = strUnHex(strPk);
if (!pkHex)
return tefINTERNAL;
auto const pkType = publicKeyType(makeSlice(*pkHex));
if (!pkType)
return tefINTERNAL;
PublicKey const pk (makeSlice(*pkHex));
// check on ledger
if (auto const unlRep = ctx.view.read(keylet::UNLReport()); unlRep)
{
auto const& vlKeys = unlRep->getFieldArray(sfImportVLKeys);
for (auto const& k: vlKeys)
if (PublicKey(k[sfPublicKey]) == pk)
return tesSUCCESS;
}
return telIMPORT_VL_KEY_NOT_RECOGNISED;
}
TER

View File

@@ -143,13 +143,15 @@ XRPNotCreated::visitEntry(
bool
XRPNotCreated::finalize(
STTx const& tx,
TER const,
TER const res,
XRPAmount const fee,
ReadView const& view,
beast::Journal const& j)
{
if (view.rules().enabled(featureImport) && tx.getTxnType() == ttIMPORT)
auto const tt = tx.getTxnType();
if (view.rules().enabled(featureImport) && tt == ttIMPORT && res == tesSUCCESS)
{
// different rules for ttIMPORT
auto const [inner, meta] = Import::getInnerTxn(tx, j);
@@ -176,6 +178,24 @@ XRPNotCreated::finalize(
return (drops_ == dropsAdded.drops() - fee.drops());
}
if (view.rules().enabled(featureXahauGenesis) && tt == ttGENESIS_MINT && res == tesSUCCESS)
{
// different rules for ttGENESIS_MINT
auto const& dests = tx.getFieldArray(sfGenesisMints);
XRPAmount dropsAdded { beast::zero };
for (auto const& dest: dests)
dropsAdded += dest.getFieldAmount(sfAmount).xrp();
JLOG(j.trace())
<< "Invariant XRPNotCreated GenesisMint: "
<< "dropsAdded: " << dropsAdded
<< " fee.drops(): " << fee.drops()
<< " drops_: " << drops_
<< " dropsAdded - fee.drops(): " << dropsAdded - fee.drops();
return (drops_ == dropsAdded.drops() - fee.drops());
}
// The net change should never be positive, as this would mean that the
// transaction created XRP out of thin air. That's not possible.
if (drops_ > 0)
@@ -426,6 +446,7 @@ LedgerEntryTypesMatch::visitEntry(
case ltNFTOKEN_OFFER:
case ltURI_TOKEN:
case ltIMPORT_VLSEQ:
case ltUNL_REPORT:
break;
default:
invalidTypeAdded_ = true;
@@ -518,16 +539,16 @@ ValidNewAccountRoot::finalize(
if (accountsCreated_ == 0)
return true;
if (accountsCreated_ > 1)
auto tt = tx.getTxnType();
if (accountsCreated_ > 1 && tt != ttGENESIS_MINT)
{
JLOG(j.fatal()) << "Invariant failed: multiple accounts "
"created in a single transaction";
return false;
}
// From this point on we know exactly one account was created.
auto tt = tx.getTxnType();
if ((tt == ttPAYMENT || tt == ttIMPORT) && result == tesSUCCESS)
if ((tt == ttPAYMENT || tt == ttIMPORT || tt == ttGENESIS_MINT) && result == tesSUCCESS)
{
std::uint32_t const startingSeq{
view.rules().enabled(featureDeletableAccounts) ? view.seq() : 1};

View File

@@ -44,6 +44,7 @@
#include <ripple/app/tx/impl/Import.h>
#include <ripple/app/tx/impl/Invoke.h>
#include <ripple/app/tx/impl/URIToken.h>
#include <ripple/app/tx/impl/GenesisMint.h>
namespace ripple {
@@ -140,6 +141,7 @@ invoke_preflight(PreflightContext const& ctx)
case ttAMENDMENT:
case ttFEE:
case ttUNL_MODIFY:
case ttUNL_REPORT:
case ttEMIT_FAILURE:
return invoke_preflight_helper<Change>(ctx);
case ttHOOK_SET:
@@ -156,6 +158,8 @@ invoke_preflight(PreflightContext const& ctx)
return invoke_preflight_helper<NFTokenAcceptOffer>(ctx);
case ttCLAIM_REWARD:
return invoke_preflight_helper<ClaimReward>(ctx);
case ttGENESIS_MINT:
return invoke_preflight_helper<GenesisMint>(ctx);
case ttIMPORT:
return invoke_preflight_helper<Import>(ctx);
case ttINVOKE:
@@ -261,6 +265,7 @@ invoke_preclaim(PreclaimContext const& ctx)
case ttAMENDMENT:
case ttFEE:
case ttUNL_MODIFY:
case ttUNL_REPORT:
case ttEMIT_FAILURE:
return invoke_preclaim<Change>(ctx);
case ttNFTOKEN_MINT:
@@ -275,6 +280,8 @@ invoke_preclaim(PreclaimContext const& ctx)
return invoke_preclaim<NFTokenAcceptOffer>(ctx);
case ttCLAIM_REWARD:
return invoke_preclaim<ClaimReward>(ctx);
case ttGENESIS_MINT:
return invoke_preclaim<GenesisMint>(ctx);
case ttIMPORT:
return invoke_preclaim<Import>(ctx);
case ttINVOKE:
@@ -339,6 +346,7 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
case ttAMENDMENT:
case ttFEE:
case ttUNL_MODIFY:
case ttUNL_REPORT:
case ttEMIT_FAILURE:
return Change::calculateBaseFee(view, tx);
case ttNFTOKEN_MINT:
@@ -353,6 +361,8 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
return NFTokenAcceptOffer::calculateBaseFee(view, tx);
case ttCLAIM_REWARD:
return ClaimReward::calculateBaseFee(view, tx);
case ttGENESIS_MINT:
return GenesisMint::calculateBaseFee(view, tx);
case ttIMPORT:
return Import::calculateBaseFee(view, tx);
case ttINVOKE:
@@ -496,7 +506,8 @@ invoke_apply(ApplyContext& ctx)
}
case ttAMENDMENT:
case ttFEE:
case ttUNL_MODIFY:
case ttUNL_MODIFY:
case ttUNL_REPORT:
case ttEMIT_FAILURE: {
Change p(ctx);
return p();
@@ -525,6 +536,10 @@ invoke_apply(ApplyContext& ctx)
ClaimReward p(ctx);
return p();
}
case ttGENESIS_MINT: {
GenesisMint p(ctx);
return p();
}
case ttIMPORT: {
Import p(ctx);
return p();

View File

@@ -25,6 +25,7 @@
#include <ripple/basics/base_uint.h>
#include <ripple/beast/net/IPEndpoint.h>
#include <ripple/beast/utility/Journal.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/SystemParameters.h> // VFALCO Breaks levelization
#include <boost/beast/core/string.hpp>
#include <boost/filesystem.hpp> // VFALCO FIX: This include should not be here
@@ -150,7 +151,7 @@ public:
std::vector<std::string> IPS_FIXED; // Fixed Peer IPs from rippled.cfg.
std::vector<std::string> SNTP_SERVERS; // SNTP servers from rippled.cfg.
std::vector<std::string> IMPORT_VL_KEYS;
std::map<std::string, PublicKey> IMPORT_VL_KEYS; // hex string -> class PublicKey (for caching purposes)
enum StartUpType { FRESH, NORMAL, LOAD, LOAD_FILE, REPLAY, NETWORK };
StartUpType START_UP = NORMAL;
@@ -176,10 +177,6 @@ public:
int RELAY_UNTRUSTED_VALIDATIONS = 1;
int RELAY_UNTRUSTED_PROPOSALS = 0;
// The number of ledgers worth of valdiations to keep for the purpose
// of servicing xpop requests these are lost on restart
std::optional<uint32_t> XPOP_HISTORY;
// True to ask peers not to relay current IP.
bool PEER_PRIVATE = false;
// peers_max is a legacy configuration, which is going to be replaced

View File

@@ -99,7 +99,6 @@ struct ConfigSection
#define SECTION_LEDGER_REPLAY "ledger_replay"
#define SECTION_BETA_RPC_API "beta_rpc_api"
#define SECTION_SWEEP_INTERVAL "sweep_interval"
#define SECTION_XPOP_HISTORY "xpop_history"
#define SECTION_NETWORK_ID "network_id"
#define SECTION_IMPORT_VL_KEYS "import_vl_keys"

View File

@@ -502,9 +502,6 @@ Config::loadFromString(std::string const& fileContents)
std::string strTemp;
if (getSingleSection(secConfig, SECTION_XPOP_HISTORY, strTemp, j_))
XPOP_HISTORY = beast::lexicalCastThrow<uint32_t>(strTemp);
if (getSingleSection(secConfig, SECTION_NETWORK_ID, strTemp, j_))
{
if (strTemp == "main")
@@ -907,15 +904,22 @@ Config::loadFromString(std::string const& fileContents)
auto iniFile = parseIniFile(data, true);
if (auto importKeys =
getIniFileSection(iniFile, SECTION_IMPORT_VL_KEYS))
IMPORT_VL_KEYS = *importKeys;
else
Throw<std::runtime_error>(
"The file specified in [" SECTION_VALIDATORS_FILE
"] "
"does not contain a [" SECTION_IMPORT_VL_KEYS
"] section: " +
validatorsFile.string());
getIniFileSection(iniFile, SECTION_IMPORT_VL_KEYS); importKeys)
{
for (std::string const& strPk : *importKeys)
{
auto pkHex = strUnHex(strPk);
if (!pkHex)
Throw<std::runtime_error>(
"Import VL Key '" + strPk + "' was not valid hex.");
auto const pkType = publicKeyType(makeSlice(*pkHex));
if (!pkType)
Throw<std::runtime_error>(
"Import VL Key '" + strPk + "' was not a valid key type.");
IMPORT_VL_KEYS.emplace(strPk, makeSlice(*pkHex));
}
}
if (RUN_STANDALONE)
break;

View File

@@ -74,7 +74,7 @@ namespace detail {
// Feature.cpp. Because it's only used to reserve storage, and determine how
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
// the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures = 63;
static constexpr std::size_t numFeatures = 64;
/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated
@@ -351,6 +351,7 @@ extern uint256 const fixUniversalNumber;
extern uint256 const fixNonFungibleTokensV1_2;
extern uint256 const fixNFTokenRemint;
extern uint256 const featureImport;
extern uint256 const featureXahauGenesis;
} // namespace ripple

View File

@@ -111,6 +111,9 @@ fees() noexcept;
Keylet const&
negativeUNL() noexcept;
Keylet const&
UNLReport() noexcept;
/** The beginning of an order book */
struct book_t
{

View File

@@ -172,6 +172,13 @@ enum LedgerEntryType : std::uint16_t
*/
ltURI_TOKEN = 0x0055,
/** A ledger object that reports on the active dUNL validators
* that were validating for more than 240 of the last 256 ledgers
*
* \sa keylet::UNLReport
*/
ltUNL_REPORT = 0x0052,
//---------------------------------------------------------------------------
/** A special type, matching any ledger entry type.

View File

@@ -37,7 +37,7 @@ namespace ripple {
@ingroup protocol
*/
/** Smallest legal byte size of a transaction. */
std::size_t constexpr txMinSizeBytes = 32;
std::size_t constexpr txMinSizeBytes = 10;
/** Largest legal byte size of a transaction. */
std::size_t constexpr txMaxSizeBytes = megabytes(1);

View File

@@ -477,6 +477,8 @@ extern SF_UINT256 const sfHookSetTxnID;
extern SF_UINT256 const sfOfferID;
extern SF_UINT256 const sfEscrowID;
extern SF_UINT256 const sfURITokenID;
extern SF_UINT256 const sfGovernanceFlags;
extern SF_UINT256 const sfGovernanceMarks;
// currency amount (common)
extern SF_AMOUNT const sfAmount;
@@ -581,6 +583,8 @@ extern SField const sfHookExecution;
extern SField const sfHookDefinition;
extern SField const sfHookParameter;
extern SField const sfHookGrant;
extern SField const sfActiveValidator;
extern SField const sfImportVLKey;
// array of objects (common)
// ARRAY/1 is reserved for end of array
@@ -594,6 +598,7 @@ extern SField const sfAffectedNodes;
extern SField const sfMemos;
extern SField const sfNFTokens;
extern SField const sfHooks;
extern SField const sfGenesisMint;
// array of objects (uncommon)
extern SField const sfMajorities;
@@ -603,6 +608,9 @@ extern SField const sfHookExecution;
extern SField const sfHookParameters;
extern SField const sfHooks;
extern SField const sfHookGrants;
extern SField const sfGenesisMints;
extern SField const sfActiveValidators;
extern SField const sfImportVLKeys;
//------------------------------------------------------------------------------

View File

@@ -85,7 +85,8 @@ constexpr std::ratio<204, 256> preFixAmendmentMajorityCalcThreshold;
constexpr std::ratio<80, 100> postFixAmendmentMajorityCalcThreshold;
/** The minimum amount of time an amendment must hold a majority */
constexpr std::chrono::seconds const defaultAmendmentMajorityTime = days{5};
constexpr std::chrono::seconds const defaultAmendmentMajorityTime = std::chrono::seconds{16};
//days{5};
} // namespace ripple

View File

@@ -146,7 +146,12 @@ enum TxType : std::uint16_t
ttURITOKEN_CREATE_SELL_OFFER = 48,
ttURITOKEN_CANCEL_SELL_OFFER = 49,
/** This transaciton accepts a proof of burn from an external network as a basis
/** This transaction can only be used by the genesis account, which is controlled exclusively by
* rewards/governance hooks, to print new XRP to be delivered directly to an array of destinations,
* according to reward schedule */
ttGENESIS_MINT = 96,
/** This transaction accepts a proof of burn from an external network as a basis
* for minting according to featureImport */
ttIMPORT = 97,
@@ -175,6 +180,7 @@ enum TxType : std::uint16_t
*/
ttUNL_MODIFY = 102,
ttEMIT_FAILURE = 103,
ttUNL_REPORT = 104,
};
// clang-format on

View File

@@ -454,9 +454,10 @@ REGISTER_FIX (fixNonFungibleTokensV1_2, Supported::yes, VoteBehavior::De
REGISTER_FIX (fixNFTokenRemint, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(Hooks, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(BalanceRewards, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(PaychanAndEscrowForTokens, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(URIToken, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(Import, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(PaychanAndEscrowForTokens, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(URIToken, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(Import, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(XahauGenesis, Supported::yes, VoteBehavior::DefaultYes);
// The following amendments are obsolete, but must remain supported
// because they could potentially get enabled.

View File

@@ -71,6 +71,7 @@ enum class LedgerNameSpace : std::uint16_t {
NFTOKEN_SELL_OFFERS = 'i',
URI_TOKEN = 'U',
IMPORT_VLSEQ = 'I',
UNL_REPORT = 'R',
// No longer used or supported. Left here to reserve the space
// to avoid accidental reuse.
@@ -236,6 +237,14 @@ negativeUNL() noexcept
return ret;
}
Keylet const&
UNLReport() noexcept
{
static Keylet const ret{
ltUNL_REPORT, indexHash(LedgerNameSpace::UNL_REPORT)};
return ret;
}
Keylet
book_t::operator()(Book const& b) const
{

View File

@@ -125,6 +125,29 @@ InnerObjectFormats::InnerObjectFormats()
{sfNFTokenID, soeREQUIRED},
{sfURI, soeOPTIONAL},
});
add(sfGenesisMint.jsonName.c_str(),
sfGenesisMint.getCode(),
{
{sfDestination, soeREQUIRED},
{sfAmount, soeOPTIONAL},
{sfGovernanceFlags, soeOPTIONAL},
{sfGovernanceMarks, soeOPTIONAL},
});
add(sfActiveValidator.jsonName.c_str(),
sfActiveValidator.getCode(),
{
{sfPublicKey, soeREQUIRED},
{sfAccount, soeOPTIONAL},
});
add(sfImportVLKey.jsonName.c_str(),
sfImportVLKey.getCode(),
{
{sfPublicKey, soeREQUIRED},
{sfAccount, soeOPTIONAL},
});
}
InnerObjectFormats const&

View File

@@ -63,6 +63,8 @@ LedgerFormats::LedgerFormats()
{sfRewardAccumulator, soeOPTIONAL},
{sfFirstNFTokenSequence, soeOPTIONAL},
{sfImportSequence, soeOPTIONAL},
{sfGovernanceFlags, soeOPTIONAL},
{sfGovernanceMarks, soeOPTIONAL},
},
commonFields);
@@ -298,6 +300,16 @@ LedgerFormats::LedgerFormats()
},
commonFields);
add(jss::UNLReport,
ltUNL_REPORT,
{
{sfImportVLKeys, soeOPTIONAL},
{sfActiveValidators, soeOPTIONAL},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
},
commonFields);
add(jss::EmittedTxn,
ltEMITTED_TXN,
{

View File

@@ -230,6 +230,8 @@ CONSTRUCT_TYPED_SFIELD(sfHookSetTxnID, "HookSetTxnID", UINT256,
CONSTRUCT_TYPED_SFIELD(sfOfferID, "OfferID", UINT256, 34);
CONSTRUCT_TYPED_SFIELD(sfEscrowID, "EscrowID", UINT256, 35);
CONSTRUCT_TYPED_SFIELD(sfURITokenID, "URITokenID", UINT256, 36);
CONSTRUCT_TYPED_SFIELD(sfGovernanceFlags, "GovernanceFlags", UINT256, 99);
CONSTRUCT_TYPED_SFIELD(sfGovernanceMarks, "GovernanceMarks", UINT256, 98);
// currency amount (common)
CONSTRUCT_TYPED_SFIELD(sfAmount, "Amount", AMOUNT, 1);
@@ -336,6 +338,9 @@ CONSTRUCT_UNTYPED_SFIELD(sfHookExecution, "HookExecution", OBJECT,
CONSTRUCT_UNTYPED_SFIELD(sfHookDefinition, "HookDefinition", OBJECT, 22);
CONSTRUCT_UNTYPED_SFIELD(sfHookParameter, "HookParameter", OBJECT, 23);
CONSTRUCT_UNTYPED_SFIELD(sfHookGrant, "HookGrant", OBJECT, 24);
CONSTRUCT_UNTYPED_SFIELD(sfGenesisMint, "GenesisMint", OBJECT, 96);
CONSTRUCT_UNTYPED_SFIELD(sfActiveValidator, "ActiveValidator", OBJECT, 95);
CONSTRUCT_UNTYPED_SFIELD(sfImportVLKey, "ImportVLKey", OBJECT, 94);
// array of objects
// ARRAY/1 is reserved for end of array
@@ -356,6 +361,9 @@ CONSTRUCT_UNTYPED_SFIELD(sfDisabledValidators, "DisabledValidators", ARRAY,
CONSTRUCT_UNTYPED_SFIELD(sfHookExecutions, "HookExecutions", ARRAY, 18);
CONSTRUCT_UNTYPED_SFIELD(sfHookParameters, "HookParameters", ARRAY, 19);
CONSTRUCT_UNTYPED_SFIELD(sfHookGrants, "HookGrants", ARRAY, 20);
CONSTRUCT_UNTYPED_SFIELD(sfGenesisMints, "GenesisMints", ARRAY, 96);
CONSTRUCT_UNTYPED_SFIELD(sfActiveValidators, "ActiveValidators", ARRAY, 95);
CONSTRUCT_UNTYPED_SFIELD(sfImportVLKeys, "ImportVLKeys", ARRAY, 94);
// clang-format on

View File

@@ -564,7 +564,7 @@ isPseudoTx(STObject const& tx)
return false;
auto tt = safe_cast<TxType>(*t);
return tt == ttAMENDMENT || tt == ttFEE || tt == ttUNL_MODIFY || tt == ttEMIT_FAILURE;
return tt == ttAMENDMENT || tt == ttFEE || tt == ttUNL_MODIFY || tt == ttEMIT_FAILURE || tt == ttUNL_REPORT;
}
} // namespace ripple

View File

@@ -192,6 +192,16 @@ TxFormats::TxFormats()
},
commonFields);
add(jss::UNLReport,
ttUNL_REPORT,
{
{sfLedgerSequence, soeREQUIRED},
{sfActiveValidator, soeOPTIONAL},
{sfImportVLKey, soeOPTIONAL},
},
commonFields);
add(jss::TicketCreate,
ttTICKET_CREATE,
{
@@ -358,6 +368,13 @@ TxFormats::TxFormats()
},
commonFields);
add(jss::GenesisMint,
ttGENESIS_MINT,
{
{sfGenesisMints, soeREQUIRED},
},
commonFields);
add(jss::Import,
ttIMPORT,
{

View File

@@ -72,6 +72,7 @@ JSS(FeeSettings); // ledger type.
JSS(FIELDS); // out: RPC server_definitions
JSS(Flags); // in/out: TransactionSign; field.
JSS(incomplete_shards); // out: OverlayImpl, PeerImp
JSS(GenesisMint); // tt
JSS(HookApiVersion); // field
JSS(HookHash); // field
JSS(HookNamespace); // field
@@ -116,6 +117,7 @@ JSS(RippleState); // ledger type.
JSS(SLE_hit_rate); // out: GetCounts.
JSS(SetFee); // transaction type.
JSS(UNLModify); // transaction type.
JSS(UNLReport); // transaction type.
JSS(SettleDelay); // in: TransactionSign
JSS(SendMax); // in: TransactionSign
JSS(Sequence); // in/out: TransactionSign; field.

View File

@@ -752,7 +752,7 @@ voteAndCheck(
std::size_t expect,
PreVote const& pre = defaultPreVote)
{
NegativeUNLVote vote(myId, history.env.journal);
NegativeUNLVote vote(myId, history.env.journal, history.env.app());
pre(vote);
auto txSet = std::make_shared<SHAMap>(
SHAMapType::TRANSACTION, history.env.app().getNodeFamily());
@@ -773,7 +773,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
jtx::Env env(*this);
NodeID myId(0xA0);
NegativeUNLVote vote(myId, env.journal);
NegativeUNLVote vote(myId, env.journal, env.app());
// one add, one remove
auto txSet = std::make_shared<SHAMap>(
@@ -797,7 +797,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
jtx::Env env(*this);
NodeID myId(0xA0);
NegativeUNLVote vote(myId, env.journal);
NegativeUNLVote vote(myId, env.journal, env.app());
uint256 pad_0(0);
uint256 pad_f = ~pad_0;
@@ -834,7 +834,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
if (history.goodHistory)
{
NegativeUNLVote vote(
history.UNLNodeIDs[3], history.env.journal);
history.UNLNodeIDs[3], history.env.journal, history.env.app());
BEAST_EXPECT(!vote.buildScoreTable(
history.lastLedger(),
history.UNLNodeIDSet,
@@ -849,7 +849,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
if (history.goodHistory)
{
NegativeUNLVote vote(
history.UNLNodeIDs[3], history.env.journal);
history.UNLNodeIDs[3], history.env.journal, history.env.app());
BEAST_EXPECT(!vote.buildScoreTable(
history.lastLedger(),
history.UNLNodeIDSet,
@@ -872,7 +872,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
history.UNLNodeIDs[idx] == myId &&
l->seq() % 2 == 0);
});
NegativeUNLVote vote(myId, history.env.journal);
NegativeUNLVote vote(myId, history.env.journal, history.env.app());
BEAST_EXPECT(!vote.buildScoreTable(
history.lastLedger(),
history.UNLNodeIDSet,
@@ -915,7 +915,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
history.validations.add(badNode, v2);
}
NegativeUNLVote vote(myId, history.env.journal);
NegativeUNLVote vote(myId, history.env.journal, history.env.app());
// local node still on wrong chain, can build a scoreTable,
// but all other nodes' scores are zero
@@ -954,7 +954,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
[&](std::shared_ptr<Ledger const> const& l,
std::size_t idx) -> bool { return true; });
NegativeUNLVote vote(
history.UNLNodeIDs[3], history.env.journal);
history.UNLNodeIDs[3], history.env.journal, history.env.app());
auto scoreTable = vote.buildScoreTable(
history.lastLedger(),
history.UNLNodeIDSet,
@@ -1031,7 +1031,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
for (auto const& n : history.UNLNodeIDs)
goodScoreTable[n] = NegativeUNLVote::negativeUNLHighWaterMark + 1;
NegativeUNLVote vote(history.UNLNodeIDs[0], history.env.journal);
NegativeUNLVote vote(history.UNLNodeIDs[0], history.env.journal, history.env.app());
{
// all good scores
@@ -1165,7 +1165,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
jtx::Env env(*this);
NodeID myId(0xA0);
NegativeUNLVote vote(myId, env.journal);
NegativeUNLVote vote(myId, env.journal, env.app());
std::array<std::uint32_t, 3> unlSizes = {34, 35, 80};
std::array<std::uint32_t, 3> nUnlPercent = {0, 50, 100};
@@ -1338,7 +1338,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
jtx::Env env(*this);
NodeID myId(0xA0);
NegativeUNLVote vote(myId, env.journal);
NegativeUNLVote vote(myId, env.journal, env.app());
// test cases:
// newValidators_ of the NegativeUNLVote empty, add one
@@ -1451,7 +1451,7 @@ class NegativeUNLVoteScoreTable_test : public beast::unit_test::suite
return add_50 || add_100 || add_me;
});
NegativeUNLVote vote(myId, history.env.journal);
NegativeUNLVote vote(myId, history.env.journal, history.env.app());
auto scoreTable = vote.buildScoreTable(
history.lastLedger(),
history.UNLNodeIDSet,