Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix stake/unstake #62

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions example/stake_transaction_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// import 'package:witnet/node.dart';
import 'package:witnet/schema.dart';
import 'package:witnet/src/constants.dart';
import 'package:witnet/src/utils/transformations/transformations.dart';
import 'package:witnet/witnet.dart';

var outputPointer = OutputPointer.fromString(
'0000000000000000000000000000000000000000000000000000000000000000:0');

void main() async {
/// connect to local node rpc
// NodeClient nodeClient = NodeClient(address: "127.0.0.1", port: 21338);

// String mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
/// load node xprv for the default mnemonic
Xprv masterNode = Xprv.fromXprv(
"xprv1qpujxsyd4hfu0dtwa524vac84e09mjsgnh5h9crl8wrqg58z5wmsuqqcxlqmar3fjhkprndzkpnp2xlze76g4hu7g7c4r4r2m2e6y8xlvu566tn6");

Xprv withdrawer = masterNode /
KEYPATH_PURPOSE /
KEYPATH_COIN_TYPE /
KEYPATH_ACCOUNT /
EXTERNAL_KEYCHAIN /
0;

/// The 20 byte Public Key Hash of the withdrawer
String pkh = bytesToHex(withdrawer.privateKey.publicKey.publicKeyHash);

/// The authorization by the node
KeyedSignature authorization = signHash(pkh, masterNode.privateKey);

/// Build the Stake Key
StakeKey stakeKey = StakeKey(
validator: authorization.publicKey.pkh,
withdrawer: PublicKeyHash.fromAddress(withdrawer.address.address),
);

/// build stake transaction body
StakeBody body = StakeBody(
inputs: [
Input(outputPointer: outputPointer),
],
output: StakeOutput(
value: MINIMUM_STAKEABLE_AMOUNT_WITS,
key: stakeKey,
authorization: authorization,
),
);

/// build and sign stake transaction
StakeTransaction stake = StakeTransaction(
body: body,
signatures: [signHash(body.transactionId, masterNode.privateKey)]);

/// The Stake Transaction ID
print(stake.transactionID);

/// send stake transaction
/// var response = await nodeClient.inventory(stake.jsonMap());
///
UnstakeBody unstakeBody = UnstakeBody(
operator: PublicKeyHash.fromAddress(withdrawer.address.address),
withdrawal: ValueTransferOutput.fromJson({
"pkh": withdrawer.address.address,
"time_lock": 0,
"value": 1,
}));

KeyedSignature unstakeSignature =
signHash(bytesToHex(unstakeBody.hash), masterNode.privateKey);
UnstakeTransaction unstake =
UnstakeTransaction(body: unstakeBody, signature: unstakeSignature);

print(unstake.transactionID);
}

/// Sign Hash
KeyedSignature signHash(String hash, WitPrivateKey privateKey) {
final sig = privateKey.signature(hash);
return KeyedSignature(
publicKey: PublicKey(bytes: privateKey.publicKey.encode()),
signature: Signature(secp256k1: Secp256k1Signature(der: sig.encode())),
);
}
5 changes: 4 additions & 1 deletion lib/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ export 'src/constants.dart'
KEYPATH_PURPOSE,
KEYPATH_COIN_TYPE,
EXTERNAL_KEYCHAIN,
INTERNAL_KEYCHAIN;
INTERNAL_KEYCHAIN,
STAKE_OUTPUT_WEIGHT,
UNSTAKE_OUTPUT_WEIGHT,
MINIMUM_STAKEABLE_AMOUNT_WITS;
3 changes: 3 additions & 0 deletions lib/src/constants.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const INPUT_SIZE = 133;
const OUTPUT_SIZE = 36;
const STAKE_OUTPUT_WEIGHT = 105;
const UNSTAKE_OUTPUT_WEIGHT = 153;
const MINIMUM_STAKEABLE_AMOUNT_WITS = 10000;
const COMMIT_WEIGHT = 400;
const REVEAL_WEIGHT = 200;
const TALLY_WEIGHT = 100;
Expand Down
1 change: 1 addition & 0 deletions lib/src/crypto/hd_wallet/extended_private_key.dart
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ class Xprv extends ExtendedKey {
}

factory Xprv.fromEncryptedXprv(String xprv, String password) {
// throw 'Error from encrypted xprv';
try {
Bech32 bech = bech32.decode(xprv);
List<int> checksum = createChecksum(bech.hrp, bech.data);
Expand Down
8 changes: 7 additions & 1 deletion lib/src/data_structures/utxo_pool.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class UtxoPool {
}

List<Utxo> sortUtxos(UtxoSelectionStrategy utxoSelectionStrategy) {
print(utxoSelectionStrategy);
print('...sortUtxos.. 0. ${map.values.toList().length}');
List<Utxo> sortedUtxos;
switch (utxoSelectionStrategy) {
case UtxoSelectionStrategy.Random:
Expand Down Expand Up @@ -68,13 +70,16 @@ class UtxoPool {
});
}
}
print('...sortUtxos.. 1. ${sortedUtxos.length}');
return sortedUtxos;
}

List<Utxo> cover({
required int amountNanoWit,
required UtxoSelectionStrategy utxoStrategy,
}) {
print('-------cover UTXOS-------');
print('amount nanowit: $amountNanoWit');
List<Utxo> utxos = sortUtxos(utxoStrategy);
if (utxos.isEmpty) {
throw 'No UTXOS to select';
Expand All @@ -86,7 +91,8 @@ class UtxoPool {
});

List<Utxo> selectedUtxos = [];

print('utxos value: $utxoValue');
print('Insufficient funds?? ${amountNanoWit > utxoValue}');
if (amountNanoWit > utxoValue) {
throw 'Insufficient funds';
}
Expand Down
4 changes: 4 additions & 0 deletions lib/src/schema/public_key.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ class PublicKey extends GeneratedMessage {

Uint8List get pbBytes => writeToBuffer();

PublicKeyHash get pkh => PublicKeyHash(
hash: sha256(data: Uint8List.fromList(publicKey)).sublist(0, 20),
);

@TagNumber(1)
List<int> get publicKey => $_getN(0);
@TagNumber(1)
Expand Down
10 changes: 9 additions & 1 deletion lib/src/schema/schema.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import 'package:witnet/constants.dart'
REVEAL_WEIGHT,
TALLY_WEIGHT,
INPUT_SIZE,
OUTPUT_SIZE;
OUTPUT_SIZE,
STAKE_OUTPUT_WEIGHT,
UNSTAKE_OUTPUT_WEIGHT;

import '../../radon.dart' show radToCbor, cborToRad;

Expand Down Expand Up @@ -54,6 +56,12 @@ part 'reveal_body.dart';
part 'reveal_transaction.dart';
part 'secp256k1_signature.dart';
part 'signature.dart';
part 'stake_body.dart';
part 'stake_key.dart';
part 'stake_output.dart';
part 'stake_transaction.dart';
part 'unstake_body.dart';
part 'unstake_transaction.dart';
part 'string_pair.dart';
part 'super_block.dart';
part 'super_block_vote.dart';
Expand Down
113 changes: 113 additions & 0 deletions lib/src/schema/stake_body.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
part of 'schema.dart';

class StakeBody extends GeneratedMessage {
static final BuilderInfo _i = BuilderInfo('StakeBody',
package: const PackageName('witnet'), createEmptyInstance: create)
..pc<Input>(1, 'inputs', PbFieldType.PM, subBuilder: Input.create)
..aOM<StakeOutput>(2, 'output', subBuilder: StakeOutput.create)
..aOM<ValueTransferOutput>(3, 'change',
subBuilder: ValueTransferOutput.create)
..hasRequiredFields = false;

static StakeBody create() => StakeBody._();
static PbList<StakeBody> createRepeated() => PbList<StakeBody>();
static StakeBody getDefault() =>
_defaultInstance ??= GeneratedMessage.$_defaultFor<StakeBody>(create);
static StakeBody? _defaultInstance;

StakeBody._() : super();

@override
StakeBody clone() => StakeBody()..mergeFromMessage(this);

@override
StakeBody createEmptyInstance() => create();

factory StakeBody({
Iterable<Input>? inputs,
StakeOutput? output,
ValueTransferOutput? change,
}) {
final _result = create();
if (inputs != null) {
_result.inputs.addAll(inputs);
}
if (output != null) {
_result.output = output;
}
if (change != null) {
_result.change = change;
}
return _result;
}

factory StakeBody.fromRawJson(String str) =>
StakeBody.fromJson(json.decode(str));

@override
factory StakeBody.fromBuffer(List<int> i,
[ExtensionRegistry r = ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);

@override
factory StakeBody.fromJson(Map<String, dynamic> json) => StakeBody(
inputs: List<Input>.from(json["inputs"].map((x) => Input.fromJson(x))),
output: StakeOutput.fromJson(json["output"]),
change: ValueTransferOutput.fromJson(json["change"]),
);

factory StakeBody.fromPbBytes(Uint8List buffer) =>
create()..mergeFromBuffer(buffer, ExtensionRegistry.EMPTY);

String toRawJson({bool asHex = false}) => json.encode(jsonMap(asHex: asHex));

Map<String, dynamic> jsonMap({bool asHex = false}) => {
"inputs":
List<dynamic>.from(inputs.map((x) => x.jsonMap(asHex: asHex))),
"output": output.jsonMap(asHex: asHex),
"change": change.jsonMap(asHex: asHex),
};

Uint8List get pbBytes => writeToBuffer();

Uint8List get hash => sha256(data: pbBytes);

String get transactionId => bytesToHex(hash);
// VT_weight = N * INPUT_SIZE + M * OUTPUT_SIZE + STAKE_OUTPUT_WEIGHT
int get weight =>
(inputs.length * INPUT_SIZE) + OUTPUT_SIZE + STAKE_OUTPUT_WEIGHT;

@override
BuilderInfo get info_ => _i;

@TagNumber(1)
List<Input> get inputs => $_getList(0);

@TagNumber(2)
StakeOutput get output => $_getN(1);
@TagNumber(2)
set output(StakeOutput v) {
setField(2, v);
}

@TagNumber(2)
bool hasOutput() => $_has(1);
@TagNumber(2)
void clearOutput() => clearField(2);
@TagNumber(2)
StakeOutput ensureOutput() => $_ensure(1);

@TagNumber(3)
ValueTransferOutput get change => $_getN(2);
@TagNumber(3)
set change(ValueTransferOutput v) {
setField(3, v);
}

@TagNumber(3)
bool hasChange() => $_has(2);
@TagNumber(3)
void clearChange() => clearField(3);
@TagNumber(3)
VTTransactionBody ensureChange() => $_ensure(2);
}
83 changes: 83 additions & 0 deletions lib/src/schema/stake_key.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
part of 'schema.dart';

class StakeKey extends GeneratedMessage {
static final BuilderInfo _i = BuilderInfo('StakeOutput',
package: const PackageName('witnet'), createEmptyInstance: create)
..aOM<PublicKeyHash>(1, 'validator', subBuilder: PublicKeyHash.create)
..aOM<PublicKeyHash>(2, 'withdrawer', subBuilder: PublicKeyHash.create)
..hasRequiredFields = false;

static StakeKey create() => StakeKey._();
static PbList<StakeKey> createRepeated() => PbList<StakeKey>();
static StakeKey getDefault() =>
_defaultInstance ??= GeneratedMessage.$_defaultFor<StakeKey>(create);
static StakeKey? _defaultInstance;
StakeKey._() : super();

@override
GeneratedMessage clone() => StakeKey()..mergeFromMessage(this);

@override
GeneratedMessage createEmptyInstance() => create();

factory StakeKey({
PublicKeyHash? validator,
PublicKeyHash? withdrawer,
}) {
final _result = create();
if (validator != null) {
_result.validator = validator;
}
if (withdrawer != null) {
_result.withdrawer = withdrawer;
}
return _result;
}

factory StakeKey.fromRawJson(String str) =>
StakeKey.fromJson(json.decode(str));

@override
factory StakeKey.fromJson(Map<String, dynamic> json) => StakeKey(
validator: PublicKeyHash.fromAddress(json["validator"]),
withdrawer: PublicKeyHash.fromAddress(json["withdrawer"]),
);

Map<String, dynamic> jsonMap({bool asHex = false}) => {
"validator": validator.address,
"withdrawer": withdrawer.address,
};

@override
BuilderInfo get info_ => _i;

Uint8List get pbBytes => writeToBuffer();

@TagNumber(1)
PublicKeyHash get validator => $_getN(0);
@TagNumber(1)
set validator(PublicKeyHash v) {
setField(1, v);
}

@TagNumber(1)
bool hasValidator() => $_has(0);
@TagNumber(1)
void clearValidator() => clearField(1);
@TagNumber(1)
PublicKeyHash ensureValidator() => $_ensure(0);

@TagNumber(2)
PublicKeyHash get withdrawer => $_getN(1);
@TagNumber(2)
set withdrawer(PublicKeyHash v) {
setField(1, v);
}

@TagNumber(2)
bool hasWithdrawer() => $_has(1);
@TagNumber(2)
void clearWithdrawer() => clearField(2);
@TagNumber(2)
PublicKeyHash ensureWithdrawer() => $_ensure(1);
}
Loading