From c186ea8ce9b4a8a2d34cf26c3dce8fa570c28a2e Mon Sep 17 00:00:00 2001
From: cgeek <cem.moreau@gmail.com>
Date: Tue, 14 Nov 2017 13:39:21 +0100
Subject: [PATCH] [fix] #1200 Redefine unlock conditions
(cherry picked from commit 8eb68e6)
---
.gitignore | 8 +-
app/lib/common-libs/parsers/transaction.ts | 6 +-
app/lib/common-libs/txunlock.ts | 91 ++++++++++++---
app/lib/dto/TransactionDTO.ts | 53 +++++++++
app/lib/indexer.ts | 53 ++-------
app/lib/rules/global_rules.ts | 76 ++++++++-----
app/lib/rules/local_rules.ts | 34 +-----
doc/Protocol.md | 107 +++++++++++++++---
test/fast/modules/common/grammar-test.js | 76 -------------
test/fast/modules/common/grammar.ts | 84 ++++++++++++++
test/integration/crosschain-test.js | 4 +-
test/integration/transactions-csv-cltv-sig.ts | 76 +++++++++++++
test/integration/transactions-test.js | 4 +-
13 files changed, 451 insertions(+), 221 deletions(-)
delete mode 100644 test/fast/modules/common/grammar-test.js
create mode 100644 test/fast/modules/common/grammar.ts
create mode 100644 test/integration/transactions-csv-cltv-sig.ts
diff --git a/.gitignore b/.gitignore
index 8d7e9c280..db4b1103d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,7 +42,11 @@ test/blockchain/lib/*.d.ts
/server.d.ts
app/**/*.js*
app/**/*.d.ts
-test/integration/*.js*
+test/integration/revoked_pubkey_replay.js
+test/integration/server-shutdown.js
+test/integration/transactions-csv-cltv-sig.js
+test/integration/ws2p*js
+test/integration/*.js.map
test/integration/*.d.ts
test/integration/membership_chainability.js*
test/integration/membership_chainability.d.ts
@@ -58,3 +62,5 @@ test/fast/proxies*.js*
test/fast/proxies*.d.ts
test/fast/modules/ws2p/*.js*
test/fast/modules/ws2p/*.d.ts
+test/fast/modules/common/grammar.js*
+test/fast/modules/common/grammar.d.ts
diff --git a/app/lib/common-libs/parsers/transaction.ts b/app/lib/common-libs/parsers/transaction.ts
index 6fe526908..e7c96e93e 100644
--- a/app/lib/common-libs/parsers/transaction.ts
+++ b/app/lib/common-libs/parsers/transaction.ts
@@ -1,7 +1,7 @@
-import {CommonConstants} from "../../../lib/common-libs/constants"
+import {CommonConstants} from "../constants"
import {GenericParser} from "./GenericParser"
import {rawer} from "../../../lib/common-libs/index"
-import { unlock } from '../txunlock';
+import {checkGrammar} from '../txunlock';
export class TransactionParser extends GenericParser {
@@ -106,7 +106,7 @@ function extractOutputs(raw:string) {
for (const line of lines) {
if (line.match(CommonConstants.TRANSACTION.TARGET)) {
outputs.push(line);
- const unlocked = unlock(line.split(':')[2], [], {})
+ const unlocked = checkGrammar(line.split(':')[2])
if (unlocked === null) {
throw Error("Wrong output format")
}
diff --git a/app/lib/common-libs/txunlock.ts b/app/lib/common-libs/txunlock.ts
index f01c2bafe..f37f88a2b 100644
--- a/app/lib/common-libs/txunlock.ts
+++ b/app/lib/common-libs/txunlock.ts
@@ -1,8 +1,8 @@
-"use strict";
import {hashf} from "../common"
+import {evalParams} from "../rules/global_rules"
+import {TxSignatureResult} from "../dto/TransactionDTO"
-let Parser = require("jison").Parser;
-let buid = require('../../../app/lib/common-libs/buid').Buid
+let Parser = require("jison").Parser
let grammar = {
"lex": {
@@ -42,36 +42,91 @@ let grammar = {
[ "( e )", "$$ = $2;" ]
]
}
-};
+}
-export function unlock(conditionsStr:string, executions:any, metadata:any): boolean|null {
+export interface UnlockMetadata {
+ currentTime?:number
+ elapsedTime?:number
+}
- let parser = new Parser(grammar);
+export function unlock(conditionsStr:string, unlockParams:string[], sigResult:TxSignatureResult, metadata?:UnlockMetadata): boolean|null {
+
+ const params = evalParams(unlockParams, conditionsStr, sigResult)
+ let parser = new Parser(grammar)
+ let nbFunctions = 0
parser.yy = {
i: 0,
sig: function (pubkey:string) {
- let sigParam = executions[this.i++];
- return (sigParam && pubkey === sigParam.pubkey && sigParam.sigOK) || false;
+ // Counting functions
+ nbFunctions++
+ // Make the test
+ let success = false
+ let i = 0
+ while (!success && i < params.length) {
+ const p = params[i]
+ success = p.successful && p.funcName === 'SIG' && p.parameter === pubkey
+ i++
+ }
+ return success
},
xHx: function(hash:string) {
- let xhxParam = executions[this.i++];
- if (xhxParam === undefined) {
- xhxParam = ""
+ // Counting functions
+ nbFunctions++
+ // Make the test
+ let success = false
+ let i = 0
+ while (!success && i < params.length) {
+ const p = params[i]
+ success = p.successful && p.funcName === 'XHX' && hashf(p.parameter) === hash
+ i++
}
- return hashf(xhxParam) === hash;
+ return success
},
cltv: function(deadline:string) {
- return metadata.currentTime && metadata.currentTime >= parseInt(deadline);
+ // Counting functions
+ nbFunctions++
+ // Make the test
+ return metadata && metadata.currentTime && metadata.currentTime >= parseInt(deadline)
},
csv: function(amountToWait:string) {
- return metadata.elapsedTime && metadata.elapsedTime >= parseInt(amountToWait);
+ // Counting functions
+ nbFunctions++
+ // Make the test
+ return metadata && metadata.elapsedTime && metadata.elapsedTime >= parseInt(amountToWait)
}
- };
+ }
+
+ try {
+ const areAllValidParameters = params.reduce((success, p) => success && !!(p.successful), true)
+ if (!areAllValidParameters) {
+ throw "All parameters must be successful"
+ }
+ const unlocked = parser.parse(conditionsStr)
+ if (unlockParams.length > nbFunctions) {
+ throw "There must be at most as much params as function calls"
+ }
+ return unlocked
+ } catch(e) {
+ return null
+ }
+}
+
+export function checkGrammar(conditionsStr:string): boolean|null {
+
+ let parser = new Parser(grammar);
+
+ parser.yy = {
+ i: 0,
+ sig: () => true,
+ xHx: () => true,
+ cltv: () => true,
+ csv: () => true
+ }
try {
- return parser.parse(conditionsStr);
+ return parser.parse(conditionsStr)
} catch(e) {
- return null;
+ return null
}
-}
\ No newline at end of file
+}
diff --git a/app/lib/dto/TransactionDTO.ts b/app/lib/dto/TransactionDTO.ts
index 15e65a4a2..b70cfbaca 100644
--- a/app/lib/dto/TransactionDTO.ts
+++ b/app/lib/dto/TransactionDTO.ts
@@ -1,5 +1,6 @@
import {hashf} from "../common"
import {Cloneable} from "./Cloneable"
+import {verify} from "../common-libs/crypto/keyring"
export interface BaseDTO {
base: number
@@ -25,6 +26,32 @@ export class OutputDTO implements BaseDTO {
) {}
}
+export interface TxSignatureResult {
+ sigs:{
+ k:string
+ ok:boolean
+ }[]
+}
+
+export class TxSignatureResultImpl implements TxSignatureResult {
+
+ // The signature results
+ public sigs:{
+ k:string
+ ok:boolean
+ }[]
+
+ constructor(issuers:string[]) {
+ this.sigs = issuers.map(k => {
+ return { k, ok: false }
+ })
+ }
+
+ get allMatching() {
+ return this.sigs.reduce((ok, s) => ok && s.ok, true)
+ }
+}
+
export class TransactionDTO implements Cloneable {
clone(): any {
@@ -197,6 +224,24 @@ export class TransactionDTO implements Cloneable {
}
}
+ getTransactionSigResult() {
+ const sigResult = new TxSignatureResultImpl(this.issuers.slice())
+ let i = 0
+ const raw = this.getRawTxNoSig()
+ let matching = true
+ while (matching && i < this.signatures.length) {
+ const sig = this.signatures[i]
+ const pub = this.issuers[i]
+ sigResult.sigs[i].ok = matching = verify(raw, sig, pub)
+ i++
+ }
+ return sigResult
+ }
+
+ checkSignatures() {
+ return this.getTransactionSigResult().allMatching
+ }
+
static fromJSONObject(obj:any, currency:string = "") {
return new TransactionDTO(
obj.version || 10,
@@ -276,6 +321,14 @@ export class TransactionDTO implements Cloneable {
}
}
+ static unlock2params(unlock:string) {
+ const match = unlock.match(/^\d+:(.*)$/)
+ if (match) {
+ return match[1].split(' ')
+ }
+ return []
+ }
+
static mock() {
return new TransactionDTO(1, "", 0, "", "", 0, [], [], [], [], [], "")
}
diff --git a/app/lib/indexer.ts b/app/lib/indexer.ts
index 7b9c17140..ddb1cfa99 100644
--- a/app/lib/indexer.ts
+++ b/app/lib/indexer.ts
@@ -1,4 +1,3 @@
-"use strict";
import {BlockDTO} from "./dto/BlockDTO"
import {ConfDTO, CurrencyConfDTO} from "./dto/ConfDTO"
import {IdentityDTO} from "./dto/IdentityDTO"
@@ -6,11 +5,11 @@ import {RevocationDTO} from "./dto/RevocationDTO"
import {CertificationDTO} from "./dto/CertificationDTO"
import {TransactionDTO} from "./dto/TransactionDTO"
import {DBHead} from "./db/DBHead"
-import {LOCAL_RULES_HELPERS} from "./rules/local_rules"
import {verify} from "./common-libs/crypto/keyring"
import {rawer, txunlock} from "./common-libs/index"
import {CommonConstants} from "./common-libs/constants"
import {MembershipDTO} from "./dto/MembershipDTO"
+import {UnlockMetadata} from "./common-libs/txunlock"
const _ = require('underscore');
@@ -1777,7 +1776,7 @@ export class Indexer {
reduce: reduce,
reduceBy: reduceBy,
getMaxBlockSize: (HEAD: DBHead) => Math.max(500, Math.ceil(1.1 * HEAD.avgBlockSize)),
- checkPeopleAreNotOudistanced: checkPeopleAreNotOudistanced
+ checkPeopleAreNotOudistanced
}
}
@@ -1979,44 +1978,14 @@ async function checkCertificationIsValid (block: BlockDTO, cert: CindexEntry, fi
function txSourceUnlock(ENTRY:SindexEntry, source:SindexEntry, HEAD: DBHead) {
const tx = ENTRY.txObj;
- let sigResults = LOCAL_RULES_HELPERS.getSigResult(tx)
- let unlocksForCondition = [];
- let unlocksMetadata: any = {};
- let unlockValues = ENTRY.unlock;
- if (source.conditions) {
- if (unlockValues) {
- // Evaluate unlock values
- let sp = unlockValues.split(' ');
- for (const func of sp) {
- const match = func.match(/\((.+)\)/)
- let param = match && match[1];
- if (param && func.match(/SIG/)) {
- let pubkey = tx.issuers[parseInt(param)];
- if (!pubkey) {
- return false;
- }
- unlocksForCondition.push({
- pubkey: pubkey,
- sigOK: sigResults.sigs[pubkey] && sigResults.sigs[pubkey].matching || false
- });
- } else {
- // XHX
- unlocksForCondition.push(param);
- }
- }
- }
-
- if (source.conditions.match(/CLTV/)) {
- unlocksMetadata.currentTime = HEAD.medianTime;
- }
-
- if (source.conditions.match(/CSV/)) {
- unlocksMetadata.elapsedTime = HEAD.medianTime - source.written_time;
- }
-
- if (txunlock(source.conditions, unlocksForCondition, unlocksMetadata)) {
- return true;
- }
+ const unlockParams:string[] = TransactionDTO.unlock2params(ENTRY.unlock || '')
+ const unlocksMetadata:UnlockMetadata = {}
+ const sigResult = TransactionDTO.fromJSONObject(tx).getTransactionSigResult()
+ if (source.conditions.match(/CLTV/)) {
+ unlocksMetadata.currentTime = HEAD.medianTime;
+ }
+ if (source.conditions.match(/CSV/)) {
+ unlocksMetadata.elapsedTime = HEAD.medianTime - source.written_time;
}
- return false;
+ return txunlock(source.conditions, unlockParams, sigResult, unlocksMetadata)
}
diff --git a/app/lib/rules/global_rules.ts b/app/lib/rules/global_rules.ts
index 665b087f9..761dcf3dc 100644
--- a/app/lib/rules/global_rules.ts
+++ b/app/lib/rules/global_rules.ts
@@ -1,17 +1,16 @@
-"use strict";
import {ConfDTO} from "../dto/ConfDTO"
import {FileDAL} from "../dal/fileDAL"
import {DBBlock} from "../db/DBBlock"
-import {TransactionDTO} from "../dto/TransactionDTO"
-import * as local_rules from "./local_rules"
+import {TransactionDTO, TxSignatureResult} from "../dto/TransactionDTO"
import {BlockDTO} from "../dto/BlockDTO"
import {verify} from "../common-libs/crypto/keyring"
import {rawer, txunlock} from "../common-libs/index"
import {CommonConstants} from "../common-libs/constants"
import {IdentityDTO} from "../dto/IdentityDTO"
+import {hashf} from "../common"
+import {Indexer} from "../indexer"
-const _ = require('underscore');
-const indexer = require('../indexer').Indexer
+const _ = require('underscore')
const constants = CommonConstants
@@ -23,6 +22,40 @@ let logger = {
// TODO: all the global rules should be replaced by index rule someday
+export interface ParamEval {
+ successful:boolean
+ funcName:string
+ parameter:string
+}
+
+export function evalParams(params:string[], conditions = '', sigResult:TxSignatureResult): ParamEval[] {
+ const res:ParamEval[] = []
+ const issuers = sigResult.sigs.map(s => s.k)
+ for (const func of params) {
+ if (func.match(/^SIG/)) {
+ const param = (func.match(/^SIG\((.*)\)$/) as string[])[1]
+ const index = parseInt(param)
+ const sigEntry = !isNaN(index) && index < issuers.length && sigResult.sigs[index]
+ const signatory:{ k:string, ok:boolean } = sigEntry || { k: '', ok: false }
+ res.push({
+ funcName: 'SIG',
+ parameter: signatory.k,
+ successful: signatory.ok
+ })
+ }
+ else if (func.match(/^XHX/)) {
+ const password = (func.match(/^XHX\((.*)\)$/) as string[])[1]
+ const hash = hashf(password)
+ res.push({
+ funcName: 'XHX',
+ parameter: password,
+ successful: conditions.indexOf('XHX(' + hash + ')') !== -1
+ })
+ }
+ }
+ return res
+}
+
export const GLOBAL_RULES_FUNCTIONS = {
checkIdentitiesAreWritable: async (block:{ identities:string[], version: number }, conf:ConfDTO, dal:FileDAL) => {
@@ -90,31 +123,10 @@ export const GLOBAL_RULES_FUNCTIONS = {
if (block.medianTime - dbSrc.written_time < tx.locktime) {
throw constants.ERRORS.LOCKTIME_PREVENT;
}
- let sigResults = local_rules.LOCAL_RULES_HELPERS.getSigResult(tx);
- let unlocksForCondition = [];
+ let unlockValues = unlocks[k]
+ let unlocksForCondition:string[] = (unlockValues || '').split(' ')
let unlocksMetadata:any = {};
- let unlockValues = unlocks[k];
if (dbSrc.conditions) {
- if (unlockValues) {
- // Evaluate unlock values
- let sp = unlockValues.split(' ');
- for (const func of sp) {
- let param = func.match(/\((.+)\)/)[1];
- if (func.match(/^SIG/)) {
- let pubkey = tx.issuers[parseInt(param)];
- if (!pubkey) {
- logger.warn('Source ' + [src.amount, src.base, src.type, src.identifier, src.pos].join(':') + ' unlock fail (unreferenced signatory)');
- throw constants.ERRORS.WRONG_UNLOCKER;
- }
- unlocksForCondition.push({
- pubkey: pubkey,
- sigOK: sigResults.sigs[pubkey] && sigResults.sigs[pubkey].matching || false
- });
- } else if (func.match(/^XHX/)) {
- unlocksForCondition.push(param);
- }
- }
- }
if (dbSrc.conditions.match(/CLTV/)) {
unlocksMetadata.currentTime = block.medianTime;
@@ -124,14 +136,18 @@ export const GLOBAL_RULES_FUNCTIONS = {
unlocksMetadata.elapsedTime = block.medianTime - dbSrc.written_time;
}
+ const sigs = tx.getTransactionSigResult()
+
try {
- if (!txunlock(dbSrc.conditions, unlocksForCondition, unlocksMetadata)) {
+ if (!txunlock(dbSrc.conditions, unlocksForCondition, sigs, unlocksMetadata)) {
throw Error('Locked');
}
} catch (e) {
logger.warn('Source ' + [src.amount, src.base, src.type, src.identifier, src.pos].join(':') + ' unlock fail');
throw constants.ERRORS.WRONG_UNLOCKER;
}
+ } else {
+ throw Error("Source with no conditions")
}
}
let sumOfOutputs = outputs.reduce(function(p, output) {
@@ -167,7 +183,7 @@ export const GLOBAL_RULES_HELPERS = {
return Promise.resolve(false);
}
try {
- return indexer.DUP_HELPERS.checkPeopleAreNotOudistanced([member], newLinks, newcomers, conf, dal);
+ return Indexer.DUP_HELPERS.checkPeopleAreNotOudistanced([member], newLinks, newcomers, conf, dal);
} catch (e) {
return true;
}
diff --git a/app/lib/rules/local_rules.ts b/app/lib/rules/local_rules.ts
index b2cfb00c3..41d19f163 100644
--- a/app/lib/rules/local_rules.ts
+++ b/app/lib/rules/local_rules.ts
@@ -374,9 +374,8 @@ export const LOCAL_RULES_FUNCTIONS = {
const txs = block.transactions
// Check rule against each transaction
for (const tx of txs) {
- let sigResult = getSigResult(tx);
- if (!sigResult.matching) {
- throw Error('Signature from a transaction must match');
+ if (!tx.checkSignatures()) {
+ throw Error('Signature from a transaction must match')
}
}
return true;
@@ -387,33 +386,6 @@ function checkSingleMembershipSignature(ms:any) {
return verify(ms.getRaw(), ms.signature, ms.issuer);
}
-function getSigResult(tx:any) {
- let sigResult:any = { sigs: {}, matching: true };
- let json:any = { "version": tx.version, "currency": tx.currency, "blockstamp": tx.blockstamp, "locktime": tx.locktime, "inputs": [], "outputs": [], "issuers": tx.issuers, "signatures": [], "comment": tx.comment, unlocks: [] };
- tx.inputs.forEach(function (input:any) {
- json.inputs.push(input.raw);
- });
- tx.outputs.forEach(function (output:any) {
- json.outputs.push(output.raw);
- });
- json.unlocks = tx.unlocks;
- let i = 0;
- let signaturesMatching = true;
- const raw = tx.getRawTxNoSig()
- while (signaturesMatching && i < tx.signatures.length) {
- const sig = tx.signatures[i];
- const pub = tx.issuers[i];
- signaturesMatching = verify(raw, sig, pub);
- sigResult.sigs[pub] = {
- matching: signaturesMatching,
- index: i
- };
- i++;
- }
- sigResult.matching = signaturesMatching;
- return sigResult;
-}
-
function checkBunchOfTransactions(transactions:TransactionDTO[], done:any = undefined){
const block:any = { transactions };
return (async () => {
@@ -439,8 +411,6 @@ export const LOCAL_RULES_HELPERS = {
checkSingleMembershipSignature: checkSingleMembershipSignature,
- getSigResult: getSigResult,
-
checkBunchOfTransactions: checkBunchOfTransactions,
checkSingleTransactionLocally: (tx:any, done:any = undefined) => checkBunchOfTransactions([tx], done),
diff --git a/doc/Protocol.md b/doc/Protocol.md
index ee06c1e47..b657553f4 100644
--- a/doc/Protocol.md
+++ b/doc/Protocol.md
@@ -475,14 +475,28 @@ It follows a machine-readable BNF grammar composed of
#### Condition matching
-Each `Unlock` of TX2 refers to an input of TX2 through `IN_INDEX`, input itself refering to an `Output` of TX1 through `T_HASH` reference and `T_INDEX`.
+Considering a transaction `TX` consuming some money: each unlock line `UNLOCK` of TX tries to unlock the input `INPUT = TX.Inputs[IN_INDEX]` refering to an ouput `Output` whose value is:
-* An output contains `F` functions in its conditions (read from left to right)
-* An unlock contains `P` parameters (or less, min. is zero), each separated by a space (read from left to right)
+If `INPUT.TYPE = 'T'`:
-A function of TX1 at position `f` returns TRUE if parameter at position `p` resolves the function. Otherwise it returns FALSE.
+ Output = TRANSACTIONS_IN_BLOCKCHAIN_OR_IN_LOCAL_BLOCK[T_HASH].OUPUTS[T_INDEX]
-The condition of an `Output` is unlocked if, evaluated globally with `(`, `)`, `&&`, and `||`, the condition returns TRUE.
+If `INPUT.TYPE = 'D'`:
+
+ Output = BLOCKCHAIN[BLOCK_ID].Dividend[PUBLIC_KEY]
+
+Let's name:
+
+* `CONDITIONS` the conditions of a given output `Output`
+* `NB_FUNCTIONS` the number of functions present in `CONDITIONS` (read from left to right)
+* `NB_PARAMETERS` the number of parameters present in `UNLOCK`, each separated by a space (read from left to right)
+
+Then, an `UNLOCK` line is considered *successful* if:
+
+* `Output` exists
+* `NB_PARAMETERS <= NB_FUNCTIONS`
+* Each `UNLOCK` parameter returns TRUE
+* `CONDITIONS` evaluated globally with `(`, `)`, `&&`, `||` and its locking functions returns TRUE
##### Example 1
@@ -524,18 +538,35 @@ Unlocks:
Because `SIG(1)` refers to the signature `DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo`, considering that signature `DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo` is good over TX2.
-#### Unlocking functions
+#### Locking and unlocking functions
-These functions may be present under both `Unlocks` and `Outputs` fields.
+The *locking* functions are present under `Outputs` field.
-* When present under `Outputs`, these functions define the *necessary conditions* to spend each output.
-* When present under `Unlocks`, these functions define the *sufficient proofs* that each input can be spent.
+The *unlocking* functions are present under `Unlocks` field.
##### SIG function
+###### Definition
+
+Lock:
+
+ SIG(PUBKEY_A)
+
+Unlock:
+
+ SIG(INDEX)
+
+###### Condition
+
+Lock `SIG(PUBKEY_A)` returns TRUE if it exists a parameter `SIG(INDEX)` returning TRUE where `Issuers[INDEX] == PUBKEY_A`.
+
+Unlock `SIG(INDEX)` returns TRUE if `Signatures[INDEX]` is a valid valid signature of TX against `Issuers[INDEX]` public key.
+
+###### Description
+
This function is a control over the signature.
-* in an `Output` of TX1, `SIG(PUBKEY_A)` requires from a future transaction TX2 unlocking the output to give as parameter a valid signature of TX2 by `PUBKEY_A`
+* in an `Output` of a transaction TX1, `SIG(PUBKEY_A)` requires from a future transaction TX2 unlocking the output to give as parameter a valid signature of TX2 by `PUBKEY_A`
* if TX2 does not give `SIG(INDEX)` parameter as [matching parameter](#condition-matching), the condition fails
* if TX2's `Issuers[INDEX]` does not equal `PUBKEY_A`, the condition fails
* if TX2's `SIG(INDEX)` does not return TRUE, the condition fails
@@ -578,6 +609,24 @@ The necessary condition `SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)` is m
##### XHX function
+###### Definition
+
+Lock:
+
+ XHX(HASH)
+
+Unlock:
+
+ XHX(PASSWORD)
+
+###### Condition
+
+`XHX(HASH)` returns true if it exists a parameter `XHX(PASSWORD)` where `SHA256(PASSWORD) = HASH`.
+
+`XHX(PASSWORD)` returns true if it exists a locking function `XHX(HASH)` where `SHA256(PASSWORD) = HASH` in the conditions.
+
+###### Description
+
This function is a password control.
So if we have, in TX1:
@@ -608,9 +657,9 @@ Where:
* `6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3` is the hash of TX1.
-The necessary condition `XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB)` is matched here if `XHX(1872767826647264) = 8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB`.
+Then `XHX(PASSWORD)` returns TRUE if it exists `XHX(HASH)` in the output's conditions of TX1, with the relation `SHA256(PASSWORD) = HASH`.
-`XHX(1872767826647264)` is to be evaluated as `SHA256(1872767826647264)`.
+Here `XHX(1872767826647264)` returns TRUE if it exists `XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB)` in the output's conditions of TX1, as it matches the link `SHA256(1872767826647264) = 8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB`.
#### Example 1
@@ -710,6 +759,20 @@ Signatures (fakes here):
##### CLTV function
+###### Definition
+
+Lock:
+
+ CLVT(TIMESTAMP)
+
+Unlock: no unlocking function.
+
+###### Condition
+
+`CLVT(TIMESTAMP)` returns true if and only if the transaction including block's `MedianTime >= TIMESTAMP`.
+
+###### Description
+
This function locks an output in the future, which will be unlocked at a given date.
So if we have, in TX1:
@@ -722,12 +785,28 @@ Outputs
25:2:CLTV(1489677041)
```
-Then the `25` units can be spent *exclusively* in a block whose `MedianTime >= 1489677041`
+So here, the `25` units can be spent *exclusively* in a block whose `MedianTime >= 1489677041`
`CLTV`'s parameter must be an integer with a length between `1` and `10` chars.
##### CSV function
+###### Definition
+
+Lock:
+
+ CSV(DELAY)
+
+Unlock: no unlocking function.
+
+We define `TxTime` as the `MedianTime` of the block referenced by the transaction's `Blockstamp` field.
+
+###### Condition
+
+`CSV(DELAY)` returns true if and only if the transaction including block's `MedianTime - TxTime >= DELAY`.
+
+###### Description
+
This function locks an output in the future, which will be unlocked after the given amount of time has elapsed.
So if we have, in TX1:
@@ -742,8 +821,6 @@ Outputs
25:2:CSV(3600)
```
-We define `TxTime` as the `MedianTime` of block `204-00003E2B8A35370BA5A7064598F628A62D4E9EC1936BE8651CE9A85F2E06981B`.
-
Then the `25` units can be spent *exclusively* in a block whose `MedianTime - TxTime >= 3600`.
`CSV`'s parameter must be an integer with a length between `1` and `8` chars.
diff --git a/test/fast/modules/common/grammar-test.js b/test/fast/modules/common/grammar-test.js
deleted file mode 100644
index 49979860e..000000000
--- a/test/fast/modules/common/grammar-test.js
+++ /dev/null
@@ -1,76 +0,0 @@
-"use strict";
-
-const unlock = require('../../../../app/lib/common-libs').txunlock
-const should = require('should');
-const assert = require('assert');
-
-describe('Grammar', () => {
-
- let k1 = "HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd";
- let k2 = "GgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd";
- let Ha = "CA978112CA1BBDCAFAC231B39A23DC4DA786EFF8147C4E72B9807785AFEE48BB";
- let Hz = "594E519AE499312B29433B7DD8A97FF068DEFCBA9755B6D5D00E84C524D67B06";
-
- it('SIG should work', () => {
-
- unlock('SIG(' + k1 + ')', [{ pubkey: k1, sigOK: true }]).should.equal(true);
- unlock('SIG(' + k1 + ')', [{ pubkey: k1, sigOK: false }]).should.equal(false);
- unlock('SIG(' + k1 + ')', [{ pubkey: k2, sigOK: false }]).should.equal(false);
- unlock('SIG(' + k1 + ')', [{ pubkey: k2, sigOK: true }]).should.equal(false);
- });
-
- it('XHX should work', () => {
-
- unlock('XHX(' + Ha + ')', ['a']).should.equal(true);
- unlock('XHX(' + Hz + ')', ['z']).should.equal(true);
- unlock('XHX(' + Hz + ')', ['a']).should.equal(false);
- unlock('XHX(' + Ha + ')', ['z']).should.equal(false);
- });
-
- it('&& keywork should work', () => {
-
- unlock('SIG(' + k1 + ') && SIG(' + k2 + ')', [{ pubkey: k1, sigOK: true }, { pubkey: k1, sigOK: true }]).should.equal(false);
- unlock('SIG(' + k1 + ') && SIG(' + k2 + ')', [{ pubkey: k1, sigOK: true }, { pubkey: k2, sigOK: false }]).should.equal(false);
- unlock('SIG(' + k1 + ') && SIG(' + k2 + ')', [{ pubkey: k1, sigOK: true }, { pubkey: k2, sigOK: true }]).should.equal(true);
- });
-
- it('|| keywork should work', () => {
-
- unlock('SIG(' + k1 + ') || SIG(' + k2 + ')', [{ pubkey: k1, sigOK: true }, { pubkey: k2, sigOK: true }]).should.equal(true);
- unlock('SIG(' + k1 + ') || SIG(' + k2 + ')', [{ pubkey: k1, sigOK: false }, { pubkey: k2, sigOK: true }]).should.equal(true);
- unlock('SIG(' + k1 + ') || SIG(' + k2 + ')', [{ pubkey: k1, sigOK: true }, { pubkey: k2, sigOK: false }]).should.equal(true);
- unlock('SIG(' + k1 + ') || SIG(' + k2 + ')', [{ pubkey: k1, sigOK: false }, { pubkey: k2, sigOK: false }]).should.equal(false);
- });
-
- it('|| && keyworks combined should work', () => {
-
- unlock('SIG(' + k1 + ') || (SIG(' + k1 + ') && SIG(' + k2 + '))', [{ pubkey: k1, sigOK: true },{ pubkey: k1, sigOK: true },{ pubkey: k2, sigOK: true }]).should.equal(true);
- unlock('SIG(' + k2 + ') || (SIG(' + k1 + ') && SIG(' + k2 + '))', [{ pubkey: k2, sigOK: false },{ pubkey: k1, sigOK: true },{ pubkey: k2, sigOK: false }]).should.equal(false);
- });
-
- it('SIG XHX functions combined should work', () => {
-
- unlock('SIG(' + k1 + ') && XHX(' + Ha + ')', [{ pubkey: k1, sigOK: true },'a']).should.equal(true);
- unlock('SIG(' + k1 + ') && XHX(' + Ha + ')', [{ pubkey: k1, sigOK: true },'z']).should.equal(false);
- unlock('SIG(' + k1 + ') || XHX(' + Ha + ')', [{ pubkey: k1, sigOK: true },'a']).should.equal(true);
- unlock('SIG(' + k1 + ') || XHX(' + Ha + ')', [{ pubkey: k1, sigOK: true },'z']).should.equal(true);
- unlock('SIG(' + k1 + ') || XHX(' + Ha + ')', [{ pubkey: k1, sigOK: false },'z']).should.equal(false);
- unlock('SIG(' + k1 + ') || XHX(' + Ha + ')', [{ pubkey: k1, sigOK: false },'a']).should.equal(true);
- });
-
- it('SIG, XHX, &&, || words combined should work', () => {
-
- unlock('SIG(' + k1 + ') && XHX(' + Ha + ') || XHX(' + Hz + ')', [{ pubkey: k1, sigOK: true },'a','z']).should.equal(true);
- unlock('SIG(' + k1 + ') && XHX(' + Ha + ') || XHX(' + Hz + ')', [{ pubkey: k1, sigOK: true },'a','a']).should.equal(true);
- unlock('SIG(' + k1 + ') && XHX(' + Ha + ') || XHX(' + Hz + ')', [{ pubkey: k1, sigOK: true },'z','a']).should.equal(false);
- unlock('SIG(' + k1 + ') && XHX(' + Ha + ') || XHX(' + Hz + ')', [{ pubkey: k1, sigOK: false },'a','a']).should.equal(false);
- unlock('SIG(' + k1 + ') && XHX(' + Ha + ') || XHX(' + Hz + ')', [{ pubkey: k1, sigOK: false },'a','z']).should.equal(true);
- unlock('SIG(' + k1 + ') && XHX(' + Ha + ') || XHX(' + Hz + ')', [{ pubkey: k1, sigOK: false },'z','z']).should.equal(true);
- unlock('(SIG(EA7Dsw39ShZg4SpURsrgMaMqrweJPUFPYHwZA8e92e3D) || XHX(03AC674216F3E15C761EE1A5E255F067953623C8B388B4459E13F978D7C846F4))', [{ pubkey: k1, sigOK: false },'1234']).should.equal(true);
- });
-
- it('Wrong syntax should return `null`', () => {
-
- assert.equal(unlock('XHX(03AC674216F3E15C761EE1A5E255F067953623C8B388B4459E13F978D7C846F4))', []), null)
- });
-});
diff --git a/test/fast/modules/common/grammar.ts b/test/fast/modules/common/grammar.ts
new file mode 100644
index 000000000..a72a0c974
--- /dev/null
+++ b/test/fast/modules/common/grammar.ts
@@ -0,0 +1,84 @@
+import {unlock} from "../../../../app/lib/common-libs/txunlock"
+
+const assert = require('assert')
+
+describe('Grammar', () => {
+
+ let k1 = "HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd"
+ let k2 = "GgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd"
+ let k3 = "IgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd"
+ let Ha = "CA978112CA1BBDCAFAC231B39A23DC4DA786EFF8147C4E72B9807785AFEE48BB"
+ let Hz = "594E519AE499312B29433B7DD8A97FF068DEFCBA9755B6D5D00E84C524D67B06"
+
+ it('SIG should work', () => {
+ assert.equal(unlock('SIG(' + k1 + ')', ['SIG(0)'], { sigs: [{ k:k1, ok:true }] }), true)
+ assert.equal(unlock('SIG(' + k1 + ')', ['SIG(0)'], { sigs: [{ k:k1, ok:false }] }), null)
+ assert.equal(unlock('SIG(' + k1 + ')', ['SIG(0)'], { sigs: [{ k:k2, ok:true }] }), false)
+ assert.equal(unlock('SIG(' + k2 + ')', ['SIG(0)'], { sigs: [{ k:k2, ok:true }] }), true)
+ })
+
+ it('SIG should work', () => {
+ assert.equal(unlock('SIG(' + k1 + ')', ['SIG(0)'], { sigs: [{ k:k1, ok:true }] }), true)
+ assert.equal(unlock('SIG(' + k1 + ')', ['SIG(0)'], { sigs: [{ k:k1, ok:false }] }), null)
+ assert.equal(unlock('SIG(' + k1 + ')', ['SIG(0)'], { sigs: [{ k:k2, ok:true }] }), false)
+ assert.equal(unlock('SIG(' + k2 + ')', ['SIG(0)'], { sigs: [{ k:k2, ok:true }] }), true)
+ })
+
+ it('XHX should work', () => {
+ assert.equal(unlock('XHX(' + Ha + ')', ['XHX(a)'], { sigs: []}), true)
+ assert.equal(unlock('XHX(' + Hz + ')', ['XHX(z)'], { sigs: []}), true)
+ assert.equal(unlock('XHX(' + Hz + ')', ['XHX(a)'], { sigs: []}), null)
+ assert.equal(unlock('XHX(' + Ha + ')', ['XHX(z)'], { sigs: []}), null)
+ })
+
+ it('&& keywork should work', () => {
+ assert.equal(unlock('SIG(' + k1 + ') && SIG(' + k2 + ')', ['SIG(0)', 'SIG(0)'], { sigs: [{ k:k1, ok:true }, { k:k1, ok:true }] }), false)
+ assert.equal(unlock('SIG(' + k1 + ') && SIG(' + k2 + ')', ['SIG(0)', 'SIG(0)'], { sigs: [{ k:k1, ok:true }, { k:k2, ok:true }] }), false)
+ assert.equal(unlock('SIG(' + k1 + ') && SIG(' + k2 + ')', ['SIG(0)', 'SIG(1)'], { sigs: [{ k:k1, ok:true }, { k:k3, ok:true }] }), false)
+ assert.equal(unlock('SIG(' + k1 + ') && SIG(' + k2 + ')', ['SIG(0)', 'SIG(1)'], { sigs: [{ k:k1, ok:true }, { k:k2, ok:true }] }), true)
+ })
+
+ it('|| keywork should work', () => {
+ assert.equal(unlock('SIG(' + k1 + ') || SIG(' + k2 + ')', ['SIG(0)'], { sigs: [{ k:k1, ok:true }] }), true)
+ assert.equal(unlock('SIG(' + k1 + ') || SIG(' + k2 + ')', ['SIG(0)'], { sigs: [{ k:k2, ok:true }] }), true)
+ assert.equal(unlock('SIG(' + k1 + ') || SIG(' + k2 + ')', ['SIG(0)', 'SIG(1)'], { sigs: [{ k:k1, ok:true }, { k:k2, ok:true }] }), true)
+ assert.equal(unlock('SIG(' + k1 + ') || SIG(' + k2 + ')', ['SIG(0)', 'SIG(1)'], { sigs: [{ k:k1, ok:false }, { k:k2, ok:false }] }), null)
+ })
+
+ it('|| && keyworks combined should work', () => {
+ assert.equal(unlock('SIG(' + k1 + ') || (SIG(' + k1 + ') && SIG(' + k2 + '))', ['SIG(0)','SIG(0)','SIG(1)'], { sigs: [{ k:k1, ok:true }, { k:k2, ok:true }] }), true)
+ assert.equal(unlock('SIG(' + k2 + ') || (SIG(' + k1 + ') && SIG(' + k2 + '))', ['SIG(0)','SIG(0)','SIG(1)'], { sigs: [{ k:k1, ok:true }, { k:k1, ok:false }, { k:k2, ok:true }] }), null)
+ })
+
+ it('SIG XHX functions combined should work', () => {
+ assert.equal(unlock('SIG(' + k1 + ') && XHX(' + Ha + ')', ['SIG(0)', 'XHX(a)'], { sigs: [{ k:k1, ok:true }] }), true)
+ assert.equal(unlock('SIG(' + k1 + ') && XHX(' + Ha + ')', ['SIG(0)', 'XHX(z)'], { sigs: [{ k:k1, ok:true }] }), null)
+ assert.equal(unlock('SIG(' + k1 + ') || XHX(' + Ha + ')', ['SIG(0)', 'XHX(a)'], { sigs: [{ k:k1, ok:true }] }), true)
+ assert.equal(unlock('SIG(' + k1 + ') || XHX(' + Ha + ')', ['SIG(0)', 'XHX(z)'], { sigs: [{ k:k1, ok:true }] }), null)
+ assert.equal(unlock('SIG(' + k1 + ') || XHX(' + Ha + ')', ['SIG(0)'], { sigs: [{ k:k1, ok:true }] }), true)
+ assert.equal(unlock('SIG(' + k1 + ') || XHX(' + Ha + ')', ['SIG(0)', 'XHX(z)'], { sigs: [{ k:k1, ok:false }] }), null)
+ assert.equal(unlock('SIG(' + k1 + ') || XHX(' + Ha + ')', ['SIG(0)', 'XHX(a)'], { sigs: [{ k:k1, ok:false }] }), null)
+ assert.equal(unlock('SIG(' + k1 + ') || XHX(' + Ha + ')', ['SIG(0)', 'XHX(a)'], { sigs: [{ k:k2, ok:true }] }), true)
+ })
+
+ it('SIG, XHX, &&, || words combined should work', () => {
+ assert.equal(unlock('SIG(' + k1 + ') && XHX(' + Ha + ') || XHX(' + Hz + ')', ['SIG(0)', 'XHX(a)', 'XHX(z)'], { sigs: [{ k:k1, ok:true }] }), true)
+ assert.equal(unlock('SIG(' + k1 + ') && XHX(' + Ha + ') || XHX(' + Hz + ')', ['SIG(0)', 'XHX(a)', 'XHX(a)'], { sigs: [{ k:k1, ok:true }] }), true)
+ assert.equal(unlock('SIG(' + k1 + ') && XHX(' + Ha + ') || XHX(' + Hz + ')', ['SIG(0)', 'XHX(z)', 'XHX(a)'], { sigs: [{ k:k1, ok:true }] }), true) // The order does not matter
+ assert.equal(unlock('SIG(' + k1 + ') && XHX(' + Ha + ') || XHX(' + Hz + ')', ['SIG(0)', 'XHX(a)', 'XHX(a)'], { sigs: [{ k:k1, ok:false }] }), null) // H(z) is false
+ assert.equal(unlock('SIG(' + k1 + ') && XHX(' + Ha + ') || XHX(' + Hz + ')', ['SIG(0)', 'XHX(a)', 'XHX(z)'], { sigs: [{ k:k2, ok:true }] }), true) // H(z) is true
+ assert.equal(unlock('SIG(' + k1 + ') && XHX(' + Ha + ') || XHX(' + Hz + ')', ['SIG(0)', 'XHX(z)', 'XHX(z)'], { sigs: [{ k:k2, ok:true }] }), true) // H(z) is true
+ assert.equal(unlock('(SIG(EA7Dsw39ShZg4SpURsrgMaMqrweJPUFPYHwZA8e92e3D) || XHX(03AC674216F3E15C761EE1A5E255F067953623C8B388B4459E13F978D7C846F4))', ['SIG(0)', 'XHX(1234)'], { sigs: [{ k:k1, ok:true }] }), true)
+ })
+
+ it('CSV+CLTV+1of2SIG', () => {
+ assert.equal(unlock('(SIG(HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd) || SIG(2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc)) && (CSV(10) || CLTV(1500000000))', ['SIG(0)'], { sigs: [{ k:'2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', ok:true }] }, {"currentTime":1499999999,"elapsedTime":9}), false)
+ assert.equal(unlock('(SIG(HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd) || SIG(2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc)) && (CSV(10) || CLTV(1500000000))', ['SIG(0)'], { sigs: [{ k:'2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', ok:true }] }, {"currentTime":1499999999,"elapsedTime":10}), true)
+ assert.equal(unlock('(SIG(HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd) || SIG(2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc)) && (CSV(10) || CLTV(1500000000))', ['SIG(0)'], { sigs: [{ k:'2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', ok:true }] }, {"currentTime":1499999999,"elapsedTime":9}), false)
+ assert.equal(unlock('(SIG(HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd) || SIG(2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc)) && (CSV(10) || CLTV(1500000000))', ['SIG(0)'], { sigs: [{ k:'2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', ok:true }] }, {"currentTime":1500000000,"elapsedTime":9}), true)
+ })
+
+ it('Wrong syntax should return `null`', () => {
+ assert.equal(unlock('XHX(03AC674216F3E15C761EE1A5E255F067953623C8B388B4459E13F978D7C846F4))', [], { sigs: [] }), null)
+ })
+})
diff --git a/test/integration/crosschain-test.js b/test/integration/crosschain-test.js
index a0e8f32ae..c00e626e5 100644
--- a/test/integration/crosschain-test.js
+++ b/test/integration/crosschain-test.js
@@ -316,13 +316,13 @@ describe("Crosschain transactions", function() {
// 1. toc secretely chooses X password
let btx1 = yield tocB.prepareUTX(btx0, ['SIG(0)'], [{ qty: 120, base: 0, lock: '(XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB) && SIG(' + ticB.pub + ')) || (SIG(' + tocB.pub + ') && SIG(' + ticB.pub + '))' }], { comment: 'BETA toc to tic', blockstamp: blockstampB });
// 2. toc makes a rollback transaction from tx1, signed by both parties (through internet): toc and tic
- let btx2 = yield tocB.prepareMTX(btx1, ticB, ['XHX(0) SIG(1) SIG(0) SIG(1)'], [{ qty: 120, base: 0, lock: 'SIG(' + tocB.pub + ')' }], { comment: 'money back to tocB in 48h', locktime: 3, blockstamp: blockstampB }); // N.B.: locktime should be like 48h in real world
+ let btx2 = yield tocB.prepareMTX(btx1, ticB, ['SIG(0) SIG(1)'], [{ qty: 120, base: 0, lock: 'SIG(' + tocB.pub + ')' }], { comment: 'money back to tocB in 48h', locktime: 3, blockstamp: blockstampB }); // N.B.: locktime should be like 48h in real world
// TICM side (META)
// 3. tic generates a transaction based on H(X) given by toc (through internet)
let mtx3 = yield ticM.prepareUTX(mtx0, ['SIG(0)'], [{ qty: 120, base: 0, lock: '(XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB) && SIG(' + tocM.pub + ')) || (SIG(' + ticM.pub + ') && SIG(' + tocM.pub + '))' }], { comment: 'META tic to toc', blockstamp: blockstampM });
// 4. tic makes a rollback transaction from tx1, signed by both parties: toc and tic
- let mtx4 = yield ticM.prepareMTX(mtx3, tocM, ['XHX(0) SIG(1) SIG(0) SIG(1)'], [{ qty: 120, base: 0, lock: 'SIG(' + ticM.pub + ')' }], { comment: 'money back to ticM', locktime: 2, blockstamp: blockstampM }); // N.B.: locktime should be like 24h in real world
+ let mtx4 = yield ticM.prepareMTX(mtx3, tocM, ['SIG(0) SIG(1)'], [{ qty: 120, base: 0, lock: 'SIG(' + ticM.pub + ')' }], { comment: 'money back to ticM', locktime: 2, blockstamp: blockstampM }); // N.B.: locktime should be like 24h in real world
// We submit TX1 to the network & write it
yield tocB.sendTX(btx1);
diff --git a/test/integration/transactions-csv-cltv-sig.ts b/test/integration/transactions-csv-cltv-sig.ts
new file mode 100644
index 000000000..4146d97d5
--- /dev/null
+++ b/test/integration/transactions-csv-cltv-sig.ts
@@ -0,0 +1,76 @@
+import {simpleNodeWith2Users, TestingServer} from "./tools/toolbox"
+import {hashf} from "../../app/lib/common"
+import {Buid} from "../../app/lib/common-libs/buid"
+
+describe("Transaction: CSV+CLTV+1of2SIG", function() {
+
+ const now = 1500000000
+ const DONT_WAIT_FOR_BLOCKCHAIN_CHANGE = true
+ let s1:TestingServer
+ let cat:any
+ let tac:any
+ let txHash:string
+
+ const conf = {
+ nbCores: 1,
+ sigQty: 1,
+ udTime0: now,
+ medianTimeBlocks: 1 // MedianTime(t) = Time(t-1)
+ }
+
+ before(async () => {
+ const res1 = await simpleNodeWith2Users(conf)
+ s1 = res1.s1
+ cat = res1.cat
+ tac = res1.tac
+ await s1.commit({ time: now })
+ await s1.commit({ time: now })
+ })
+
+ it('cat should have 100 units', async () => {
+ const sources = await s1.get('/tx/sources/' + cat.pub)
+ sources.should.have.property('sources').length(1)
+ })
+
+ it('cat should be able to make a CSV+CLTV+1of2SIG tx', async () => {
+ const current = await s1.dal.getCurrentBlockOrNull()
+ const tx = await cat.makeTX([{
+ src: '100:0:D:HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd:1',
+ unlock: 'SIG(0)'
+ }], [{
+ qty: 100,
+ base: 0,
+ lock: '(SIG(' + cat.pub + ') || SIG(' + tac.pub + ')) && (CSV(10) || CLTV(' + now + '))'
+ }], {
+ blockstamp: Buid.format.buid(current)
+ })
+ txHash = hashf(tx)
+ await cat.sendTX(tx)
+ const block = await s1.commit({ time: now }, DONT_WAIT_FOR_BLOCKCHAIN_CHANGE)
+ block.should.have.property('transactions').length(1)
+ await s1.waitToHaveBlock(2)
+ })
+
+ it('tac should be able to consume the tx after 10s', async () => {
+ await s1.commit({ time: now + 10 })
+ await s1.commit({ time: now + 10 })
+ const current = await s1.dal.getCurrentBlockOrNull()
+ const tx = await tac.makeTX([{
+ src: '100:0:T:' + txHash + ':0',
+ unlock: 'SIG(0)'
+ }], [{
+ qty: 100,
+ base: 0,
+ lock: 'SIG(' + tac.pub + ')'
+ }], {
+ blockstamp: Buid.format.buid(current)
+ })
+ await tac.sendTX(tx)
+ const block = await s1.commit(null, DONT_WAIT_FOR_BLOCKCHAIN_CHANGE)
+ block.should.have.property('transactions').length(1)
+ })
+
+ after(async () => {
+ await s1.closeCluster()
+ })
+})
diff --git a/test/integration/transactions-test.js b/test/integration/transactions-test.js
index ee5cfa156..5592ecf47 100644
--- a/test/integration/transactions-test.js
+++ b/test/integration/transactions-test.js
@@ -193,8 +193,8 @@ describe("Testing transactions", function() {
(yield s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(4); // As well as tic
let tx3 = yield tic.prepareUTX(tx2, ['XHX(1872767826647264) SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'wrong', blockstamp: [current.number, current.hash].join('-') });
let tx4 = yield toc.prepareUTX(tx2, ['XHX(1872767826647264) SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'ok', blockstamp: [current.number, current.hash].join('-') });
- let tx5 = yield tic.prepareMTX(tx2, toc, ['XHX(1872767826647264) SIG(1) SIG(0) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789) XHX(123456789)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'multi OK', blockstamp: [current.number, current.hash].join('-') });
- let tx6 = yield toc.prepareMTX(tx2, tic, ['XHX(1872767826647264) SIG(1) SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'multi WRONG', blockstamp: [current.number, current.hash].join('-') });
+ let tx5 = yield tic.prepareMTX(tx2, toc, ['XHX(1872767826647264) SIG(1) SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'multi OK', blockstamp: [current.number, current.hash].join('-') });
+ let tx6 = yield toc.prepareMTX(tx2, tic, ['XHX(1872767826647264) SIG(1) SIG(0) SIG(0) SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'multi WRONG', blockstamp: [current.number, current.hash].join('-') });
// nLocktime
let tx7 = yield tic.prepareMTX(tx2, toc, ['XHX(1872767826647264) SIG(1) SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'wrong locktime', locktime: 100, blockstamp: [current.number, current.hash].join('-') });
yield unit.shouldFail(toc.sendTX(tx3), 'Wrong unlocker in transaction');
--
GitLab