Skip to content

Commit

Permalink
add mass calculator
Browse files Browse the repository at this point in the history
  • Loading branch information
x100111010 committed Oct 9, 2024
1 parent 6f35186 commit 5dca54a
Show file tree
Hide file tree
Showing 3 changed files with 317 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -1,30 +1,99 @@
import '../utils.dart';
import 'types.dart';

bool isCoinBase({required Transaction tx}) =>
tx.subnetworkId.hex == kSubnetworkIdCoinbaseHex;
//1 byte for OP_DATA_65 + 64 (length of signature) + 1 byte for sig hash type
const kSignatureSize = 1 + 64 + 1;

class TxMassCalculator {
BigInt _max(BigInt a, BigInt b) => a > b ? a : b;

enum Kip9Version {
alpha,
beta,
}

class MassCalculator {
final int massPerTxByte;
final int massPerScriptPubKeyByte;
final int massPerSigOp;
final BigInt storageMassParameter;

static TxMassCalculator get defaultCalculator {
return TxMassCalculator(
static MassCalculator get defaultCalculator {
return MassCalculator(
massPerTxByte: 1,
massPerScriptPubKeyByte: 10,
massPerSigOp: 1000,
storageMassParameter: kStorageMassParameter,
);
}

TxMassCalculator({
MassCalculator({
required this.massPerTxByte,
required this.massPerScriptPubKeyByte,
required this.massPerSigOp,
required this.storageMassParameter,
});

int calculateMass({required Transaction tx}) {
if (isCoinBase(tx: tx)) {
BigInt calcTxOverallMass({
required Transaction tx,
Kip9Version version = Kip9Version.beta,
}) {
final computeMass = BigInt.from(calcTxComputeMass(tx: tx));
final storageMass = calcTxStorageMass(tx: tx);
return switch (version) {
Kip9Version.alpha => computeMass + storageMass,
Kip9Version.beta => _max(computeMass, storageMass),
};
}

BigInt calcTxStorageMass({
required Transaction tx,
Kip9Version version = Kip9Version.beta,
}) {
if (tx.isCoinbase) {
return BigInt.zero;
}

final harmonicOuts = tx.outputs
.map(
(output) => storageMassParameter ~/ output.value.toUnsignedBigInt(),
)
.fold(
BigInt.zero,
(total, element) => total + element,
);

final outsLen = tx.outputs.length;
final insLen = tx.inputs.length;

final isRelaxed =
outsLen == 1 || insLen == 1 || (outsLen == 2 && insLen == 2);
if (version == Kip9Version.beta && isRelaxed) {
final harmonicIns = tx.inputs
.map(
(input) => storageMassParameter ~/ input.utxoEntry.amount,
)
.fold(
BigInt.zero,
(total, element) => total + element,
);

return _max(BigInt.zero, harmonicOuts - harmonicIns);
}

final sumIns = tx.inputs
.map((input) => input.utxoEntry.amount)
.fold(BigInt.zero, (previousValue, element) => previousValue + element);

final meanIns = sumIns ~/ BigInt.from(insLen);

final arithmeticIns =
BigInt.from(insLen) * (storageMassParameter ~/ meanIns);

return _max(BigInt.zero, harmonicOuts - arithmeticIns);
}

int calcTxComputeMass({required Transaction tx}) {
if (tx.isCoinbase) {
return 0;
}

Expand Down Expand Up @@ -52,10 +121,6 @@ class TxMassCalculator {
}

int txEstimatedSerializedSize({required Transaction tx}) {
if (isCoinBase(tx: tx)) {
return 0;
}

int size = 0;
size += 2; // version
size += 8; // number of inputs
Expand All @@ -80,7 +145,7 @@ class TxMassCalculator {
int size = 0;
size += outpointEstimatedSerializedSize(); // previous outpoint
size += 8; // signature script length
size += input.signatureScript.length; // signature script
size += kSignatureSize; // input.signatureScript.length; // signature script
size += 8; // sequence (uint64)
return size;
}
Expand Down
4 changes: 4 additions & 0 deletions lib/spectre/transaction/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import '../utils.dart';
part 'types.freezed.dart';
part 'types.g.dart';

final kSompiPerSpectre = BigInt.from(100000000);
final kStorageMassParameter = kSompiPerSpectre * BigInt.from(10000);

final kMinChangeTarget = BigInt.from(20000000);
final kFeePerInput = BigInt.from(10000);
const kMaxInputsPerTransaction = 84;
Expand Down Expand Up @@ -246,6 +249,7 @@ class Transaction with _$Transaction {
gas: gas,
payload: payload?.hex,
);
bool get isCoinbase => subnetworkId.hex == kSubnetworkIdCoinbaseHex;
}

@unfreezed
Expand Down
235 changes: 235 additions & 0 deletions test/mass_calculator_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import 'dart:typed_data';

import 'package:fixnum/fixnum.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:spectrum/spectre/spectre.dart';
import 'package:spectrum/spectre/transaction/mass_calculator.dart';

void main() {
Transaction generateTxFromAmounts(
Iterable<BigInt> ins, Iterable<BigInt> outs) {
final scriptPublicKey = ScriptPublicKey(
scriptPublicKey: Uint8List(0),
version: 0,
);
final prevTxId =
'880eb9819a31821d9d2399e2f35e2433b72637e393d71ecc9b8d0250f49153c3';
final address = Address.publicKey(
prefix: AddressPrefix.spectre,
publicKey: Uint8List(32),
);
final tx = Transaction(
version: 0,
inputs: ins.indexed
.map(
(indexed) => TxInput(
previousOutpoint:
Outpoint(transactionId: prevTxId, index: indexed.$1),
sequence: Int64(0),
sigOpCount: 0,
signatureScript: Uint8List(0),
address: address,
utxoEntry: UtxoEntry(
amount: indexed.$2,
isCoinbase: false,
blockDaaScore: BigInt.zero,
scriptPublicKey: scriptPublicKey,
),
),
)
.toList(),
outputs: outs
.map(
(out) => TxOutput(
value: out.toInt64(),
scriptPublicKey: scriptPublicKey,
),
)
.toList(),
lockTime: Int64(1615462089000),
subnetworkId: Uint8List.fromList(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
),
gas: Int64(0),
);

return tx;
}

group('Test Kip9 Alpha', () {
final testVersion = Kip9Version.alpha;

test('Test mass storage', () {
final tx = generateTxFromAmounts(
[100, 200, 300].map(BigInt.from),
[300, 300].map(BigInt.from),
);

final storageMassParameter = BigInt.from(10).pow(12);

// Assert the formula: max( 0 , C·( |O|/H(O) - |I|/A(I) ) )

final calculator = MassCalculator(
massPerTxByte: 0,
massPerScriptPubKeyByte: 0,
massPerSigOp: 0,
storageMassParameter: storageMassParameter,
);

final storageMass = calculator.calcTxStorageMass(
tx: tx,
version: testVersion,
);
// Compounds from 3 to 2, with symmetric outputs and no fee, should be zero
expect(storageMass, BigInt.zero);
});
test('Test mass storage asymmetry', () {
// Create asymmetry
final tx = generateTxFromAmounts(
[100, 200, 300].map(BigInt.from),
[50, 550].map(BigInt.from),
);
final storageMassParameter = BigInt.from(10).pow(12);

final calculator = MassCalculator(
massPerTxByte: 0,
massPerScriptPubKeyByte: 0,
massPerSigOp: 0,
storageMassParameter: storageMassParameter,
);

final storageMass =
calculator.calcTxStorageMass(tx: tx, version: testVersion);
expect(
storageMass,
storageMassParameter ~/ BigInt.from(50) +
storageMassParameter ~/ BigInt.from(550) -
BigInt.from(3) * (storageMassParameter ~/ BigInt.from(200)),
);
});

test('Test mass storage more outs than ins', () {
// Create a tx with more outs than ins
final baseValue = BigInt.from(10000) * kSompiPerSpectre;
final tx = generateTxFromAmounts(
[baseValue, baseValue, baseValue * BigInt.two],
List.filled(4, baseValue));
final storageMassParameter = kStorageMassParameter;
final calculator = MassCalculator(
massPerTxByte: 0,
massPerScriptPubKeyByte: 0,
massPerSigOp: 0,
storageMassParameter: storageMassParameter,
);
final storageMass =
calculator.calcTxStorageMass(tx: tx, version: testVersion);

// Inputs are above C so they don't contribute negative mass, 4 outputs exactly equal C each charge 1
expect(storageMass, BigInt.from(4));
});
test('Test mass storage less outs than ins 2', () {
final baseValue = BigInt.from(10000) * kSompiPerSpectre;
final tx = generateTxFromAmounts(
[baseValue, baseValue, baseValue * BigInt.two],
[BigInt.from(10) * kSompiPerSpectre, ...List.filled(3, baseValue)]);
final storageMassParameter = kStorageMassParameter;
final calculator = MassCalculator(
massPerTxByte: 0,
massPerScriptPubKeyByte: 0,
massPerSigOp: 0,
storageMassParameter: storageMassParameter,
);
final storageMass = calculator.calcTxStorageMass(
tx: tx,
version: testVersion,
);
expect(storageMass, BigInt.from(1003));
});

test('Test mass storage increase values over the limit', () {
// Increase values over the lim
final baseValue = BigInt.from(10000) * kSompiPerSpectre;
final tx = generateTxFromAmounts(
[baseValue, baseValue, baseValue * BigInt.two],
List.filled(4, baseValue + BigInt.one));
final storageMassParameter = kStorageMassParameter;
final calculator = MassCalculator(
massPerTxByte: 0,
massPerScriptPubKeyByte: 0,
massPerSigOp: 0,
storageMassParameter: storageMassParameter,
);
final storageMass = calculator.calcTxStorageMass(
tx: tx,
version: testVersion,
);
expect(storageMass, BigInt.zero);
});
});

group('Test Kip9 Beta', () {
final testVersion = Kip9Version.beta;

test('Test 2:2 transaction', () {
final tx = generateTxFromAmounts(
[100, 200].map(BigInt.from),
[50, 250].map(BigInt.from),
);
final storageMassParameter = BigInt.from(10).pow(12);
// Assert the formula: max( 0 , C·( |O|/H(O) - |I|/O(I) ) )

final calculator = MassCalculator(
massPerTxByte: 0,
massPerScriptPubKeyByte: 0,
massPerSigOp: 0,
storageMassParameter: storageMassParameter,
);
final storageMass = calculator.calcTxStorageMass(
tx: tx,
version: testVersion,
);
expect(storageMass, BigInt.from(9000000000));
});
test('Test outputs equal to inputs', () {
final tx = generateTxFromAmounts(
[100, 200].map(BigInt.from),
[100, 200].map(BigInt.from),
);
final storageMassParameter = BigInt.from(10).pow(12);
// Assert the formula: max( 0 , C·( |O|/H(O) - |I|/O(I) ) )

final calculator = MassCalculator(
massPerTxByte: 0,
massPerScriptPubKeyByte: 0,
massPerSigOp: 0,
storageMassParameter: storageMassParameter,
);
final storageMass = calculator.calcTxStorageMass(
tx: tx,
version: testVersion,
);
expect(storageMass, BigInt.zero);
});

test('Test mass storage one small output', () {
final tx = generateTxFromAmounts(
[100, 200].map(BigInt.from),
[50].map(BigInt.from),
);
final storageMassParameter = BigInt.from(10).pow(12);
// Assert the formula: max( 0 , C·( |O|/H(O) - |I|/O(I) ) )

final calculator = MassCalculator(
massPerTxByte: 0,
massPerScriptPubKeyByte: 0,
massPerSigOp: 0,
storageMassParameter: storageMassParameter,
);
final storageMass = calculator.calcTxStorageMass(
tx: tx,
version: testVersion,
);
expect(storageMass, BigInt.from(5000000000));
});
});
}

0 comments on commit 5dca54a

Please sign in to comment.