Skip to content

Commit

Permalink
chore: fix npm publish (#3)
Browse files Browse the repository at this point in the history
* chore: fix npm publish

* fix: unspentoutput number

* chore: release version 1.0.0-alpha.2

* fix: fee estimate

* fix: fee error

* fix: output change

* chore: release version 1.0.0-alpha.5

* chore: release version 1.0.0

* support: fee

* release 1.0.1
  • Loading branch information
ByteZhang1024 authored Sep 20, 2024
1 parent f55a0e5 commit 8e77ce3
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 69 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/package-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ jobs:
with:
node-version: '18'
registry-url: 'https://registry.npmjs.org'
- run: npm publish -y --no-verify-access
- run: npm publish -y --no-verify-access --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
15 changes: 9 additions & 6 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
declare module '@onekeyfe/kaspacore-lib' {
declare module '@onekeyfe/kaspa-core-lib' {

function initRuntime(): Promise;
function setDebugLevel(level:number):void;
Expand Down Expand Up @@ -93,7 +93,7 @@ declare module '@onekeyfe/kaspacore-lib' {
export namespace Transaction {

static class sighash {
static sign(transaction, privateKey, sighashType, inputIndex, subscript, satoshisBN, flags, signingMethod);
// static sign(transaction, privateKey, sighashType, inputIndex, subscript, satoshisBN, flags, signingMethod);
static sighash(transaction, sighashType, inputNumber, subscript, satoshisBN, flags): Buffer;
}
class UnspentOutput {
Expand All @@ -103,7 +103,7 @@ declare module '@onekeyfe/kaspacore-lib' {
readonly txId: string;
readonly outputIndex: number;
readonly script: Script;
readonly satoshis: number;
readonly satoshis: number | string;

constructor(data: object);

Expand All @@ -114,7 +114,8 @@ declare module '@onekeyfe/kaspacore-lib' {

class Output {
readonly script: Script;
readonly satoshis: number;
readonly satoshis: number | string;
readonly satoshisBN: crypto.BN;

constructor(data: object);

Expand Down Expand Up @@ -149,12 +150,12 @@ declare module '@onekeyfe/kaspacore-lib' {
constructor(serialized ? : any);

from(utxos: Transaction.UnspentOutput[]): this;
to(address: Address[] | Address | string, amount: number): this;
to(address: Address[] | Address | string, amount: number | string): this;
change(address: Address | string): this;
fee(amount: number): this;
setVersion(version: number): this;
feePerKb(amount: number): this;
sign(privateKey: PrivateKey|PrivateKey[] | string|string[], sigtype:number, signingMethod:string|undefined): this;
// sign(privateKey: PrivateKey|PrivateKey[] | string|string[], sigtype:number, signingMethod:string|undefined): this;
applySignature(sig: crypto.Signature): this;
addInput(input: Transaction.Input): this;
addOutput(output: Transaction.Output): this;
Expand All @@ -163,6 +164,8 @@ declare module '@onekeyfe/kaspacore-lib' {
lockUntilBlockHeight(height: number): this;

getMassAndSize():{txSize:number, mass:number};
calcStorageMass():number;
calcComputeMass():number;

hasWitnesses(): boolean;
getFee(): number;
Expand Down
12 changes: 8 additions & 4 deletions lib/crypto/bn.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@ BN.One = new BN(1);
BN.Minus1 = new BN(-1);

BN.fromNumber = function(n) {
$.checkArgument(_.isNumber(n));
if(n <= 0x1fffffffffffff){
return new BN(n);
if (_.isNumber(n)) {
if (n <= 0x1fffffffffffff) {
return new BN(n);
}
return new BN(n.toString(16), 16);
} else if (_.isString(n)) {
return new BN(n, 10);
}
return new BN(n.toString(16), 16);
throw new Error('Invalid input type');
};

BN.fromString = function(str, base) {
Expand Down
2 changes: 1 addition & 1 deletion lib/privatekey.js
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ PrivateKey.prototype.toPublicKey = function () {
if (!this._pubkey) {
//this._pubkey = PublicKey.fromPrivateKey(this);
let publicKey = secp256k1.getPublicKey(this.toString(), true);
console.log("publicKey", Buffer.from(publicKey).toString('hex'));
// console.log("publicKey", Buffer.from(publicKey).toString('hex'));
this._pubkey = new PublicKey(Buffer.from(publicKey), {
network: this.network.name,
});
Expand Down
17 changes: 7 additions & 10 deletions lib/transaction/output.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,17 @@ Object.defineProperty(Output.prototype, 'satoshis', {
set: function(num) {
if (num instanceof BN) {
this._satoshisBN = num;
this._satoshis = num.toNumber();
this._satoshis = num.toString();
} else if (_.isString(num)) {
this._satoshis = parseInt(num);
this._satoshisBN = BN.fromNumber(this._satoshis);
this._satoshisBN = BN.fromString(num);
this._satoshis = num;
} else {
$.checkArgument(
JSUtil.isNaturalNumber(num),
'Output satoshis is not a natural number'
);
this._satoshisBN = BN.fromNumber(num);
this._satoshis = num;
this._satoshis = BN.fromNumber(num).toString();
}
$.checkState(
JSUtil.isNaturalNumber(this._satoshis),
Expand All @@ -77,13 +77,10 @@ Object.defineProperty(Output.prototype, 'satoshis', {
});

Output.prototype.invalidSatoshis = function() {
if (this._satoshis > MAX_SAFE_INTEGER) {
return 'transaction txout satoshis greater than max safe integer';
}
if (this._satoshis !== this._satoshisBN.toNumber()) {
if(!BN.fromString(this._satoshis).eq(this._satoshisBN)) {
return 'transaction txout satoshis has corrupted value';
}
if (this._satoshis < 0) {
if(BN.fromString(this._satoshis).lt(BN.Zero)) {
return 'transaction txout negative';
}
return false;
Expand Down Expand Up @@ -163,7 +160,7 @@ Output.prototype.inspect = function() {

Output.fromBufferReader = function(br) {
var obj = {};
obj.satoshis = br.readUInt64LEBN();
obj.satoshis = br.readUInt64LEBN().toString();
var version = br.readUInt16LE();
var size = br.readUInt64LEBN().toNumber();
if (size !== 0) {
Expand Down
139 changes: 102 additions & 37 deletions lib/transaction/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ Transaction.NLOCKTIME_MAX_VALUE = 4294967295;

// Value used for fee estimation (satoshis per kilobyte)
Transaction.FEE_PER_KB = 100000;
Transaction.STORAGE_MASS = new BN(10).pow(new BN(12));


// Safe upper bound for change address script size in bytes
Expand Down Expand Up @@ -141,12 +142,12 @@ var ioProperty = {
configurable: false,
enumerable: true,
get: function() {
return this._getInputAmount();
return this._getInputAmount().toString();
}
};
Object.defineProperty(Transaction.prototype, 'inputAmount', ioProperty);
ioProperty.get = function() {
return this._getOutputAmount();
return this._getOutputAmount().toString();
};
Object.defineProperty(Transaction.prototype, 'outputAmount', ioProperty);

Expand Down Expand Up @@ -226,7 +227,7 @@ Transaction.prototype.getSerializationError = function(opts) {

var unspent = this._getUnspentValue();
var unspentError;
if (unspent < 0) {
if (unspent.lt(BN.Zero)) {
if (!opts.disableMoreOutputThanInput) {
unspentError = new errors.Transaction.InvalidOutputAmountSum();
}
Expand All @@ -249,7 +250,7 @@ Transaction.prototype._hasFeeError = function(opts, unspent) {

if (!opts.disableLargeFees) {
var maximumFee = Math.floor(Transaction.FEE_SECURITY_MARGIN * this._estimateFee());
if (unspent > maximumFee) {
if (unspent.gt(new BN(maximumFee))) {
if (this._missingChange()) {
return new errors.Transaction.ChangeAddressMissing(
'Fee is too large and no change address was provided'
Expand All @@ -262,7 +263,7 @@ Transaction.prototype._hasFeeError = function(opts, unspent) {
}
if (!opts.disableSmallFees) {
var minimumFee = Math.ceil(this._estimateFee() / Transaction.FEE_SECURITY_MARGIN);
if (unspent < minimumFee) {
if (unspent.lt(new BN(minimumFee))) {
return new errors.Transaction.FeeError.TooSmall(
'expected more than ' + minimumFee + ' but got ' + unspent
);
Expand Down Expand Up @@ -381,17 +382,18 @@ Transaction.prototype.getStandaloneMass = function() {
}
}
Transaction.prototype.getMassAndSize = function() {
let {txSize, mass:standaloneMass} = this.getStandaloneMass();
let sigOpsCount = 0;
this.inputs.forEach(input=>{
//console.log("input.script", input.output.script.toASM())
sigOpsCount += input.output.script.getSignatureOperationsCount()
});
let {txSize} = this.getStandaloneMass();

let storageMass = this.calcStorageMass();
let computeMass = this.calcComputeMass();

//console.log("standaloneMass", standaloneMass)
//console.log("sigOpsCount", sigOpsCount)
//console.log("total MASS ", standaloneMass + sigOpsCount * Transaction.MassPerSigOp)
return {txSize, mass: standaloneMass + sigOpsCount * Transaction.MassPerSigOp};
// console.log("storageMass", storageMass)
// console.log("computeMass", computeMass)
// alpha version 11
// mass = storageMass + computeMass

// current version 10
return {txSize, mass: Math.max(storageMass, computeMass)};
}
Transaction.prototype.fromBuffer = function(buffer) {
var reader = new BufferReader(buffer);
Expand Down Expand Up @@ -931,36 +933,34 @@ Transaction.prototype._addOutput = function(output) {
/**
* Calculates or gets the total output amount in satoshis
*
* @return {Number} the transaction total output amount
* @return {BN} the transaction total output amount
*/
Transaction.prototype._getOutputAmount = function() {
if (_.isUndefined(this._outputAmount)) {
var self = this;
this._outputAmount = 0;
_.each(this.outputs, function(output) {
self._outputAmount += output.satoshis;
});
this._outputAmount = this.outputs.reduce((sum, output) => {
return sum.add(new BN(output.satoshis));
}, new BN(0));
}
return this._outputAmount;
};
};


/**
* Calculates or gets the total input amount in satoshis
*
* @return {Number} the transaction total input amount
* @return {BN} the transaction total input amount
*/
Transaction.prototype._getInputAmount = function() {
if (_.isUndefined(this._inputAmount)) {
this._inputAmount = _.sumBy(this.inputs, function(input) {
if (_.isUndefined(input.output)) {
throw new errors.Transaction.Input.MissingPreviousOutput();
}
return input.output.satoshis;
});
this._inputAmount = this.inputs.reduce((sum, input) => {
if (_.isUndefined(input.output)) {
throw new errors.Transaction.Input.MissingPreviousOutput();
}
return sum.add(new BN(input.output.satoshis));
}, new BN(0));
}
return this._inputAmount;
};
};

Transaction.prototype._updateChangeOutput = function() {
if (!this._changeScript) {
Expand All @@ -972,8 +972,8 @@ Transaction.prototype._updateChangeOutput = function() {
}
var available = this._getUnspentValue();
var fee = this.getFee();
var changeAmount = available - fee;
if (changeAmount > 0) {
var changeAmount = available.sub(new BN(fee));
if (changeAmount.gt(BN.Zero)) {
this._changeIndex = this.outputs.length;
this._addOutput(new Output({
script: this._changeScript,
Expand Down Expand Up @@ -1010,7 +1010,7 @@ Transaction.prototype.getFee = function() {
}
// if no change output is set, fees should equal all the unspent amount
if (!this._changeScript) {
return this._getUnspentValue();
return this._getUnspentValue().toNumber();
}
return this._estimateFee();
};
Expand All @@ -1028,14 +1028,14 @@ Transaction.prototype._estimateFee = function() {
}
var fee = Math.ceil(getFee(estimatedSize));
var feeWithChange = Math.ceil(getFee(estimatedSize) + getFee(Transaction.CHANGE_OUTPUT_MAX_SIZE));
if (!this._changeScript || available <= feeWithChange) {
if (!this._changeScript || available.lte(BN.fromNumber(feeWithChange))) {
return fee;
}
return feeWithChange;
};

Transaction.prototype._getUnspentValue = function() {
return this._getInputAmount() - this._getOutputAmount();
return this._getInputAmount().sub(this._getOutputAmount());
};

Transaction.prototype._clearSignatures = function() {
Expand All @@ -1044,14 +1044,33 @@ Transaction.prototype._clearSignatures = function() {
});
};

// https://github.com/kaspanet/rusty-kaspa/blob/613d082b0c2e51247819d932af7ddb5ebd5aa460/consensus/core/src/mass/mod.rs#L23-L42
Transaction.prototype._estimateSize = function() {
var result = Transaction.MAXIMUM_EXTRA_SIZE;
var result = 0;
result += 2; // Tx version (u16)
result += 8; // Number of inputs (u64)
_.each(this.inputs, function(input) {
if(input._scriptBuffer.length === 0){
result += 65; // script
}
result += input._estimateSize();
});

result += 8; // Number of outputs (u64)
_.each(this.outputs, function(output) {
result += output.script.toBuffer().length + 9;
result += 8; // value (u64)
result += 2; // output.ScriptPublicKey.Version (u16)
result += 8; // length of script public key (u64)
result += output.script.toBuffer().length;
});

result += 8; // lock time (u64)
result += 20; // subnetwork id
result += 8; // gas (u64)
result += 32; // payload hash

result += 8; // length of the payload (u64)
result += 32; // payload hash
return result;
};

Expand Down Expand Up @@ -1382,5 +1401,51 @@ Transaction.prototype.setVersion = function(version) {
return this;
};

function negativeMass(inputs, outputs_num) {
const inputs_num = inputs.length;

if (outputs_num === 1 || inputs_num === 1 || (outputs_num === 2 && inputs_num === 2)) {
return inputs.reduce((sum, v) => sum.add(Transaction.STORAGE_MASS.div(BN.fromNumber(v.output.satoshis))), new BN(0));
}

const sumInputs = inputs.reduce((sum, v) => sum.add(BN.fromNumber(v.output.satoshis)), new BN(0));
const avgInput = sumInputs.div(new BN(inputs_num));
return new BN(inputs_num).mul(Transaction.STORAGE_MASS.div(avgInput));
}

Transaction.prototype.calcStorageMass = function() {
const N = negativeMass(this.inputs, this.outputs.length);
const P = this.outputs.reduce((sum, o) =>
sum.add(Transaction.STORAGE_MASS.div(BN.fromNumber(o.satoshis))
), new BN(0));
return BN.max(P.sub(N), new BN(0));
}

Transaction.prototype.calcComputeMass = function() {
// 如果是coinbase交易,返回0
if (this.isCoinbase()) {
return 0;
}

// 计算序列化大小
const size = this._estimateSize();

// 计算序列化交易的mass
let massForSize = size * Transaction.MassPerTxByte;

// 计算所有输出的scriptPublicKey的mass
const totalScriptPublicKeySize = this.outputs.reduce((sum, output) => {
return sum + 2 + output._scriptBuffer.length;
}, 0);
const totalScriptPublicKeyMass = totalScriptPublicKeySize * Transaction.MassPerScriptPubKeyByte;

// 计算所有输入的sigOpCount的mass
const totalSigops = this.inputs.reduce((sum, input) => {
return sum + input.output.script.getSignatureOperationsCount();
}, 0);
const totalSigopsMass = totalSigops * Transaction.MassPerSigOp;

return Math.floor(massForSize + totalScriptPublicKeyMass + totalSigopsMass);
};

module.exports = Transaction;
Loading

0 comments on commit 8e77ce3

Please sign in to comment.