-
Notifications
You must be signed in to change notification settings - Fork 37
/
masternode.ts
177 lines (158 loc) · 6.16 KB
/
masternode.ts
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
import { GenesisKeys, MasterNodeKey } from '../../testkeys'
import { DockerOptions } from 'dockerode'
import { DeFiDContainer, StartOptions } from '../defid_container'
import { RegTestContainer } from './index'
/**
* RegTest with MasterNode preconfigured
*/
export class MasterNodeRegTestContainer extends RegTestContainer {
private readonly masternodeKey: MasterNodeKey
/**
* @param {string} [masternodeKey=GenesisKeys[0]] pair to use for minting
* @param {string} [image=DeFiDContainer.image] docker image name
* @param {DockerOptions} [options]
*/
constructor (masternodeKey: MasterNodeKey = GenesisKeys[0], image: string = DeFiDContainer.image, options?: DockerOptions) {
super(image, options)
this.masternodeKey = masternodeKey
}
/**
* Additional debug options turned on for traceability.
*/
protected getCmd (opts: StartOptions): string[] {
return [
...super.getCmd(opts),
'-dummypos=1',
'-nospv'
]
}
/**
* It is set to auto mint every 1 second by default in regtest.
* https://github.com/DeFiCh/ain/blob/6dc990c45788d6806ea/test/functional/test_framework/test_node.py#L160-L178
*/
async generate (nblocks: number, address: string = this.masternodeKey.operator.address, maxTries: number = 1000000): Promise<void> {
for (let minted = 0, tries = 0; minted < nblocks && tries < maxTries; tries++) {
const result = await this.call('generatetoaddress', [1, address, 1])
if (result === 1) {
minted += 1
}
}
}
/**
* This will automatically import the necessary private key for master to mint tokens
*/
async start (startOptions: StartOptions = {}): Promise<void> {
await super.start(startOptions)
// Wait for ready and setup for auto mint
await super.waitForReady(25000)
// import keys for master node
await this.call('importprivkey', [
this.masternodeKey.operator.privKey, 'coinbase', true
])
await this.call('importprivkey', [
this.masternodeKey.owner.privKey, 'coinbase', true
])
// configure the masternode
const fileContents =
'gen=1' + '\n' +
'spv=1' + '\n' +
`masternode_operator=${this.masternodeKey.operator.address}` + '\n' +
`masternode_owner=${this.masternodeKey.owner.address}`
await this.exec({
Cmd: ['bash', '-c', `echo "${fileContents}" > ~/.defi/defi.conf`]
})
await new Promise((resolve) => {
// 1 second delay before stopping due to race conditions
setTimeout(_ => resolve(0), 1000)
})
// restart and wait for ready
await this.container?.stop()
await this.container?.start()
}
/**
* Wait for master node wallet coin to be mature for spending.
*
* A coinbase transaction must be 100 blocks deep before you can spend its outputs. This is a
* safeguard to prevent outputs that originate from the coinbase transaction from becoming
* un-spendable (in the event the mined block moves out of the active chain due to a fork).
*
* @param {number} [timeout=90000] in ms
*/
async waitForWalletCoinbaseMaturity (timeout = 90000): Promise<void> {
return await this.waitForCondition(async () => {
const count = await this.getBlockCount()
if (count > 100) {
return true
}
await this.generate(1)
return false
}, timeout, 1)
}
/**
* Wait for in wallet balance to be greater than an amount.
* This allow test that require fund to wait for fund to be filled up before running the tests.
* This method will trigger block generate to get to the required balance faster.
*
* @param {number} balance to wait for in wallet to be greater than or equal
* @param {number} [timeout=30000] in ms
* @see waitForWalletCoinbaseMaturity
*/
async waitForWalletBalanceGTE (balance: number, timeout = 30000): Promise<void> {
return await this.waitForCondition(async () => {
const getbalance = await this.call('getbalance')
if (getbalance >= balance) {
return true
}
await this.generate(1)
return false
}, timeout, 1)
}
/**
* Fund an address with an amount and wait for 1 confirmation.
* Funded address don't have to be tracked within the node wallet.
* This allows for light wallet implementation testing.
*
* @param {string} address to fund
* @param {number} amount to fund an address, take note of number precision issues, BigNumber not included in pkg.
* @return {Promise<{txid: string, vout: number}>} txid and index of the transaction
* @see waitForWalletCoinbaseMaturity
* @see waitForWalletBalanceGTE
*/
async fundAddress (address: string, amount: number): Promise<{ txid: string, vout: number }> {
const txid = await this.call('sendtoaddress', [address, amount])
await this.waitForCondition(async () => {
const { confirmations } = await this.call('gettxout', [txid, 0, true])
return confirmations > 0
}, 10000)
const { vout }: {
vout: Array<{
n: number
scriptPubKey: {
addresses: string[]
}
}>
} = await this.call('getrawtransaction', [txid, true])
for (const out of vout) {
if (out.scriptPubKey.addresses.includes(address)) {
return { txid, vout: out.n }
}
}
throw new Error('getrawtransaction will always return the required vout')
}
/**
* Create a new bech32 address and get the associated priv key for it.
* The address is created in the wallet and the priv key is dumped out.
* This is to facilitate raw tx feature testing, if you need an address that is not associated with the wallet,
* use jellyfish-crypto instead.
*
* This is not a deterministic feature, each time you run this, you get a different set of address and keys.
*
* @return {Promise<{ address: string, privKey: string, pubKey: string }>} a new address and it's associated privKey
*/
async newAddressKeys (): Promise<{ address: string, privKey: string, pubKey: string }> {
const address = await this.call('getnewaddress', ['', 'bech32'])
const privKey = await this.call('dumpprivkey', [address])
const getaddressinfo = await this.call('getaddressinfo', [address])
return { address, privKey, pubKey: getaddressinfo.pubkey }
}
}