Skip to content

Commit

Permalink
TxBuilder: TransactionBuilder.nodeStake takes rewardDelegators
Browse files Browse the repository at this point in the history
  • Loading branch information
msmania committed Feb 20, 2024
1 parent 7dd626f commit d6c29c0
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 14 deletions.
3 changes: 3 additions & 0 deletions packages/transaction-builder/src/abstract-tx-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export abstract class AbstractBuilder {
* @param {string[]} chains - Network identifier list to be serviced by this node
* @param {string} amount - the amount to stake, must be greater than or equal to 1 POKT
* @param {URL} serviceURL - Node service url
* @param { [key: string]: number } rewardDelegators - Reward delegators
* @returns {MsgProtoNodeStakeTx} - The unsigned Node Stake message.
*/
abstract nodeStake({
Expand All @@ -122,12 +123,14 @@ export abstract class AbstractBuilder {
chains,
amount,
serviceURL,
rewardDelegators,
}: {
nodePubKey: string
outputAddress: string
chains: string[]
amount: string
serviceURL: URL
rewardDelegators: { [key: string]: number } | undefined
}): MsgProtoNodeStakeTx

/**
Expand Down
4 changes: 3 additions & 1 deletion packages/transaction-builder/src/factory/proto-tx-encoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ProtoStdSignature,
ProtoStdTx,
} from '../models/proto/generated/tx-signer'
import { stringifyObjectWithSort } from '@pokt-foundation/pocketjs-utils'

export class ProtoTxEncoder extends BaseTxEncoder {
public getFeeObj() {
Expand All @@ -31,7 +32,8 @@ export class ProtoTxEncoder extends BaseTxEncoder {
msg: this.msg.toStdSignDocMsgObj(),
}

return Buffer.from(JSON.stringify(stdSignDoc), 'utf-8')
// Use stringifyObject instead JSON.stringify to get a deterministic result.
return Buffer.from(stringifyObjectWithSort(stdSignDoc), 'utf-8')
}

// Returns the encoded transaction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export class MsgProtoNodeStakeTx extends TxMsg {
public readonly chains: string[]
public readonly amount: string
public readonly serviceURL: URL
public readonly rewardDelegators: { [key: string]: number } | undefined

/**
* @param {string} pubKey - Public key
Expand All @@ -30,14 +31,16 @@ export class MsgProtoNodeStakeTx extends TxMsg {
outputAddress: string,
chains: string[],
amount: string,
serviceURL: URL
serviceURL: URL,
rewardDelegators: { [key: string]: number } | undefined,
) {
super()
this.pubKey = Buffer.from(pubKey, 'hex')
this.outputAddress = Buffer.from(outputAddress, 'hex')
this.chains = chains
this.amount = amount
this.serviceURL = serviceURL
this.rewardDelegators = rewardDelegators

if (!this.serviceURL.port) {
this.serviceURL.port = '443'
Expand Down Expand Up @@ -77,18 +80,22 @@ export class MsgProtoNodeStakeTx extends TxMsg {
* @memberof MsgNodeStake
*/
public toStdSignDocMsgObj(): object {
const msg = {
chains: this.chains,
output_address: this.outputAddress.toString('hex'),
public_key: {
type: 'crypto/ed25519_public_key',
value: this.pubKey.toString('hex'),
},
service_url: this.getParsedServiceURL(),
value: this.amount,
};
if (this.rewardDelegators) {
msg['reward_delegators'] = this.rewardDelegators;
}
return {
type: this.AMINO_KEY,
value: {
chains: this.chains,
output_address: this.outputAddress.toString('hex'),
public_key: {
type: 'crypto/ed25519_public_key',
value: this.pubKey.toString('hex'),
},
service_url: this.getParsedServiceURL(),
value: this.amount,
},
value: msg,
}
}

Expand All @@ -104,6 +111,7 @@ export class MsgProtoNodeStakeTx extends TxMsg {
value: this.amount,
ServiceUrl: this.getParsedServiceURL(),
OutAddress: this.outputAddress,
RewardDelegators: this.rewardDelegators || {},
}

return Any.fromJSON({
Expand Down
134 changes: 133 additions & 1 deletion packages/transaction-builder/src/models/proto/generated/tx-signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ export interface MsgProtoNodeStake8 {
value: string;
ServiceUrl: string;
OutAddress: Uint8Array;
RewardDelegators: { [key: string]: number };
}

export interface MsgProtoNodeStake8_RewardDelegatorsEntry {
key: string;
value: number;
}

export interface MsgBeginNodeUnstake8 {
Expand Down Expand Up @@ -775,7 +781,14 @@ export const MsgUnjail = {
};

function createBaseMsgProtoNodeStake8(): MsgProtoNodeStake8 {
return { Publickey: new Uint8Array(0), Chains: [], value: "", ServiceUrl: "", OutAddress: new Uint8Array(0) };
return {
Publickey: new Uint8Array(0),
Chains: [],
value: "",
ServiceUrl: "",
OutAddress: new Uint8Array(0),
RewardDelegators: {},
};
}

export const MsgProtoNodeStake8 = {
Expand All @@ -795,6 +808,9 @@ export const MsgProtoNodeStake8 = {
if (message.OutAddress.length !== 0) {
writer.uint32(42).bytes(message.OutAddress);
}
Object.entries(message.RewardDelegators).forEach(([key, value]) => {
MsgProtoNodeStake8_RewardDelegatorsEntry.encode({ key: key as any, value }, writer.uint32(50).fork()).ldelim();
});
return writer;
},

Expand Down Expand Up @@ -840,6 +856,16 @@ export const MsgProtoNodeStake8 = {

message.OutAddress = reader.bytes();
continue;
case 6:
if (tag !== 50) {
break;
}

const entry6 = MsgProtoNodeStake8_RewardDelegatorsEntry.decode(reader, reader.uint32());
if (entry6.value !== undefined) {
message.RewardDelegators[entry6.key] = entry6.value;
}
continue;
}
if ((tag & 7) === 4 || tag === 0) {
break;
Expand All @@ -856,6 +882,12 @@ export const MsgProtoNodeStake8 = {
value: isSet(object.value) ? globalThis.String(object.value) : "",
ServiceUrl: isSet(object.ServiceUrl) ? globalThis.String(object.ServiceUrl) : "",
OutAddress: isSet(object.OutAddress) ? bytesFromBase64(object.OutAddress) : new Uint8Array(0),
RewardDelegators: isObject(object.RewardDelegators)
? Object.entries(object.RewardDelegators).reduce<{ [key: string]: number }>((acc, [key, value]) => {
acc[key] = Number(value);
return acc;
}, {})
: {},
};
},

Expand All @@ -876,6 +908,15 @@ export const MsgProtoNodeStake8 = {
if (message.OutAddress.length !== 0) {
obj.OutAddress = base64FromBytes(message.OutAddress);
}
if (message.RewardDelegators) {
const entries = Object.entries(message.RewardDelegators);
if (entries.length > 0) {
obj.RewardDelegators = {};
entries.forEach(([k, v]) => {
obj.RewardDelegators[k] = Math.round(v);
});
}
}
return obj;
},

Expand All @@ -889,6 +930,93 @@ export const MsgProtoNodeStake8 = {
message.value = object.value ?? "";
message.ServiceUrl = object.ServiceUrl ?? "";
message.OutAddress = object.OutAddress ?? new Uint8Array(0);
message.RewardDelegators = Object.entries(object.RewardDelegators ?? {}).reduce<{ [key: string]: number }>(
(acc, [key, value]) => {
if (value !== undefined) {
acc[key] = globalThis.Number(value);
}
return acc;
},
{},
);
return message;
},
};

function createBaseMsgProtoNodeStake8_RewardDelegatorsEntry(): MsgProtoNodeStake8_RewardDelegatorsEntry {
return { key: "", value: 0 };
}

export const MsgProtoNodeStake8_RewardDelegatorsEntry = {
encode(message: MsgProtoNodeStake8_RewardDelegatorsEntry, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
if (message.key !== "") {
writer.uint32(10).string(message.key);
}
if (message.value !== 0) {
writer.uint32(16).uint32(message.value);
}
return writer;
},

decode(input: _m0.Reader | Uint8Array, length?: number): MsgProtoNodeStake8_RewardDelegatorsEntry {
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseMsgProtoNodeStake8_RewardDelegatorsEntry();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1:
if (tag !== 10) {
break;
}

message.key = reader.string();
continue;
case 2:
if (tag !== 16) {
break;
}

message.value = reader.uint32();
continue;
}
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skipType(tag & 7);
}
return message;
},

fromJSON(object: any): MsgProtoNodeStake8_RewardDelegatorsEntry {
return {
key: isSet(object.key) ? globalThis.String(object.key) : "",
value: isSet(object.value) ? globalThis.Number(object.value) : 0,
};
},

toJSON(message: MsgProtoNodeStake8_RewardDelegatorsEntry): unknown {
const obj: any = {};
if (message.key !== "") {
obj.key = message.key;
}
if (message.value !== 0) {
obj.value = Math.round(message.value);
}
return obj;
},

create<I extends Exact<DeepPartial<MsgProtoNodeStake8_RewardDelegatorsEntry>, I>>(
base?: I,
): MsgProtoNodeStake8_RewardDelegatorsEntry {
return MsgProtoNodeStake8_RewardDelegatorsEntry.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<MsgProtoNodeStake8_RewardDelegatorsEntry>, I>>(
object: I,
): MsgProtoNodeStake8_RewardDelegatorsEntry {
const message = createBaseMsgProtoNodeStake8_RewardDelegatorsEntry();
message.key = object.key ?? "";
message.value = object.value ?? 0;
return message;
},
};
Expand Down Expand Up @@ -1609,6 +1737,10 @@ if (_m0.util.Long !== Long) {
_m0.configure();
}

function isObject(value: any): boolean {
return typeof value === "object" && value !== null;
}

function isSet(value: any): boolean {
return value !== null && value !== undefined;
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ message MsgProtoNodeStake8 {
string value = 3;
string ServiceUrl = 4;
bytes OutAddress = 5;
map<string, uint32> RewardDelegators = 6;
}

message MsgBeginNodeUnstake8 {
Expand Down
6 changes: 5 additions & 1 deletion packages/transaction-builder/src/tx-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ export class TransactionBuilder implements AbstractBuilder {
* @param {string[]} chains - Network identifier list to be serviced by this node
* @param {string} amount - the amount to stake, must be greater than or equal to 1 POKT
* @param {URL} serviceURL - Node service url
* @param { [key: string]: number } rewardDelegators - Reward delegators
* @returns {MsgProtoNodeStakeTx} - The unsigned Node Stake message.
*/
public nodeStake({
Expand All @@ -245,19 +246,22 @@ export class TransactionBuilder implements AbstractBuilder {
chains,
amount,
serviceURL,
rewardDelegators,
}: {
nodePubKey?: string
outputAddress?: string
chains: string[]
amount: string
serviceURL: URL
rewardDelegators?: { [key: string]: number }
}): MsgProtoNodeStakeTx {
return new MsgProtoNodeStakeTx(
nodePubKey,
outputAddress,
chains,
amount,
serviceURL
serviceURL,
rewardDelegators,
)
}

Expand Down
1 change: 1 addition & 0 deletions packages/transaction-builder/tests/transactions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ describe('TransactionBuilder Tests', () => {
chains: ['0040'],
amount: '69420000000',
serviceURL: new URL('https://mofongonodes.co:8081'),
rewardDelegators: {'6efd6a4118fc75035959c270d7a81117ec5e45c0': 1},
})
expect(nodeStakeMsg instanceof MsgProtoNodeStakeTx).toBe(true)

Expand Down
1 change: 1 addition & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './addr-from-pubkey'
export * from './public-key-from-private'
export * from './hrtime'
export * from './sort'
36 changes: 36 additions & 0 deletions packages/utils/src/sort.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

/**
* Internal function to sort an object with keys resursively
* @param {any} obj - The hex string to convert.
* @returns {Uint8Array} - A byte array with the converted hex string.
*
* */
function sortKeysRecursively(obj: any): any {
if (typeof obj !== 'object' || obj === null) {
return obj;
}

if (Array.isArray(obj)) {
return obj.map(sortKeysRecursively);
}

const sortedKeys = Object.keys(obj).sort();
const sortedObj: Record<string, any> = {};

sortedKeys.forEach(key => {
sortedObj[key] = sortKeysRecursively(obj[key]);
});

return sortedObj;
}

/**
* Stringify an object with its keys sorted alphabetically
* @param {Buffer} obj - Object to stringify
* @returns {string} - Stringified result
*/
export function stringifyObjectWithSort(obj: Record<string, any>): string {
const sortedObj = sortKeysRecursively(obj);
const jsonString = JSON.stringify(sortedObj);
return jsonString;
}
Loading

0 comments on commit d6c29c0

Please sign in to comment.