All files Sequence.ts

95.35% Statements 41/43
86.67% Branches 13/15
100% Functions 23/23
100% Lines 39/39

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210    1x   1x 1x                       1x           8x               3x               2x             1x             2x               2x 2x 2x                 2x 2x 2x             160x       136x 135x           130x             1x                 11x                 3x                 4x                   8x 3x                     3x         3x     3x     3x     3x             3x         3x   3x             100x 100x 100x             8x             4x             43x      
import { StreamReader } from "@node-lightning/bufio";
import { ICloneable } from "./ICloneable";
import { TimeLockMode } from "./TimeLockMode";
 
const MAX_SEQUENCE = 0xffff_ffff;
const DEFAULT_SEQUENCE = 0xffff_ffff;
 
/**
 * A transaction input's nSequence field according to BIP 68 where
 * rules for relative timelocks are defined. Relative timelocks prevent
 * mining of a transaction until a certain age of the spent output
 * in blocks or timespan.
 *
 * nSequence defaults to a value of 0xffff_ffff which disables the field.
 * When using a nLocktime, at least one transaction must be non-default.
 * In this condition, it is standard to use 0xffff_fffe.
 */
export class Sequence implements ICloneable<Sequence> {
    /**
     * Parses the value from a byte stream
     * @param reader
     */
    public static parse(reader: StreamReader): Sequence {
        return new Sequence(reader.readUInt32LE());
    }
 
    /**
     * Creates an nSequence value of 0xffff_fffe which is used to enable
     * nLockTime.
     */
    public static locktime(): Sequence {
        return new Sequence(0xffff_fffe);
    }
 
    /**
     * Creates an nSequence value of 0xffff_fffd which is used to
     * enable opt-in full replace-by-fee.
     */
    public static rbf(): Sequence {
        return new Sequence(0xffff_fffd);
    }
 
    /**
     * Creates an nSequence value of 0xffff_ffff
     */
    public static default(): Sequence {
        return new Sequence();
    }
 
    /**
     * Creates an nSequence value of 0
     */
    public static zero(): Sequence {
        return new Sequence(0);
    }
 
    /**
     * Creates an nSequence value with the specified block delay
     * @param blocks number of blocks sequence must wait
     */
    public static blockDelay(blocks: number): Sequence {
        const sut = new Sequence();
        sut.blockDelay = blocks;
        return sut;
    }
 
    /**
     * Creates an nSequence value with the specified time delay
     * @param seconds delay in seconds which will be rounded up to the
     * nearest 512 second interval
     */
    public static timeDelay(seconds: number): Sequence {
        const sut = new Sequence();
        sut.timeDelay = seconds;
        return sut;
    }
 
    /**
     * Gets or sets the raw nSequence value.
     */
    public get value(): number {
        return this._value;
    }
 
    public set value(val: number) {
        if (val < 0 || val > MAX_SEQUENCE) throw new Error("Invalid nSequence");
        this._value = val;
    }
 
    private _value: number;
 
    constructor(val: number = DEFAULT_SEQUENCE) {
        this.value = val;
    }
 
    /**
     * Returns true when the nSequence is the default value 0xffff_ffff
     */
    public get isDefault() {
        return this.value === DEFAULT_SEQUENCE;
    }
 
    /**
     * Returns true when the relative timelock is enabled. Technically
     * this occurs when the top-most bit is unset. This means that the
     * default value of 0xffff_ffff is unset.
     */
    public get enabled(): boolean {
        return BigInt(this.value) >> 31n === 0n;
    }
 
    /**
     * Returns true for a value that would enable nLockTime. To enable
     * nLockTime, at least one input in the transaction must have a
     * non-default nSequence value.
     */
    public get isLockTimeSignaled(): boolean {
        return this.value < DEFAULT_SEQUENCE;
    }
 
    /**
     * Returns true for a value that would signal opt-in replace-by-fee
     * as defined in BIP 125. To signal this, the nSequence must be less
     * than 0xffffffff-1.
     */
    public get isRBFSignaled(): boolean {
        return this.value < DEFAULT_SEQUENCE - 1;
    }
 
    /**
     * Gets the time lock mode for the nSequence. Technically, the bit
     * with index 22 controls the mode. When the bit is set, it will use
     * time-based relative time locks. When it is unset, it will use
     * block-based relatively time locks.
     */
    public get mode(): TimeLockMode {
        if (this.value >> 22 === 1) return TimeLockMode.Time;
        else return TimeLockMode.Block;
    }
 
    /**
     * Gets or sets a relative timelock in seconds. Time-based relative
     * time locks are encoded in 512 second granularity which is close
     * to the 600 seconds each block should be generated in. When
     * setting a value in seconds, it will rounded up to the nearest
     * 512s granularity.
     */
    public get timeDelay(): number {
        return (this.value & 0xffff) * 512;
    }
 
    public set timeDelay(seconds: number) {
        // calculate the delay in 512 second granularity
        let value = Math.ceil(seconds / 512);
 
        // if the value execeeds the max value, throw an error
        Iif (value < 0 || value > 0xffff) throw new Error("Invalid nSequence value");
 
        // set the typeto time-based relatively timelock
        value |= 1 << 22;
 
        // set the actual value
        this.value = value;
    }
 
    /**
     * Gets or set a relative timelock in blocks.
     */
    public get blockDelay(): number {
        return this.value & 0xffff;
    }
 
    public set blockDelay(blocks: number) {
        // if the value exceeds the max value, throw an error
        Iif (blocks < 0 || blocks > 0xffff) throw new Error("Invalid nSequence value");
 
        this.value = blocks;
    }
 
    /**
     * Serializes the value to a buffer
     */
    public serialize(): Buffer {
        const buf = Buffer.alloc(4);
        buf.writeUInt32LE(this.value, 0);
        return buf;
    }
 
    /**
     * Returns the raw value as a hex encoded string, eg: 0xfffffffe
     */
    public toString(): string {
        return "0x" + this.value.toString(16).padStart(8, "0");
    }
 
    /**
     * Returns the raw value as a hex encoded string, eg: 0xfffffffe
     */
    public toJSON(): string {
        return this.toString();
    }
 
    /**
     * Clone via deep copy
     */
    public clone(): Sequence {
        return new Sequence(this._value);
    }
}