mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
[NOD-66] Transaction generator (#247)
* [NOD-66] Created TX generator * [NOD-66] Created transaction generator * [NOD-66] Improved TX generator against double spend. Created genaddr utility. Refactored * [NOD-66] Save chenges before branch switch * [NOD-66] Use log package instead of fmt * [NOD-66] Fixed/restored docker files * [NOD-66] Changed according to new WithLock/NoLock convention
This commit is contained in:
parent
a3735da12a
commit
daa4481282
@ -346,12 +346,18 @@ type SequenceLock struct {
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) CalcSequenceLock(tx *util.Tx, utxoSet UTXOSet, mempool bool) (*SequenceLock, error) {
|
||||
dag.dagLock.Lock()
|
||||
defer dag.dagLock.Unlock()
|
||||
dag.dagLock.RLock()
|
||||
defer dag.dagLock.RUnlock()
|
||||
|
||||
return dag.calcSequenceLock(dag.selectedTip(), utxoSet, tx, mempool)
|
||||
}
|
||||
|
||||
// CalcSequenceLockNoLock is lock free version of CalcSequenceLockWithLock
|
||||
// This function is unsafe for concurrent access.
|
||||
func (dag *BlockDAG) CalcSequenceLockNoLock(tx *util.Tx, utxoSet UTXOSet, mempool bool) (*SequenceLock, error) {
|
||||
return dag.calcSequenceLock(dag.selectedTip(), utxoSet, tx, mempool)
|
||||
}
|
||||
|
||||
// calcSequenceLock computes the relative lock-times for the passed
|
||||
// transaction. See the exported version, CalcSequenceLock for further details.
|
||||
//
|
||||
|
@ -219,7 +219,7 @@ func TestHaveBlock(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestCalcSequenceLock tests the LockTimeToSequence function, and the
|
||||
// CalcSequenceLock method of a Chain instance. The tests exercise several
|
||||
// CalcSequenceLock method of a DAG instance. The tests exercise several
|
||||
// combinations of inputs to the CalcSequenceLock function in order to ensure
|
||||
// the returned SequenceLocks are correct for each test instance.
|
||||
func TestCalcSequenceLock(t *testing.T) {
|
||||
|
29
cmd/genaddr/genaddr.go
Normal file
29
cmd/genaddr/genaddr.go
Normal file
@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/daglabs/btcd/btcec"
|
||||
"github.com/daglabs/btcd/dagconfig"
|
||||
"github.com/daglabs/btcd/util"
|
||||
"github.com/daglabs/btcd/util/base58"
|
||||
)
|
||||
|
||||
func main() {
|
||||
activeNetParams := &dagconfig.DevNetParams
|
||||
privateKey, err := btcec.NewPrivateKey(btcec.S256())
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to generate private key: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("\nPrivate key (base-58): %s\n", base58.Encode(privateKey.Serialize()))
|
||||
serializedKey := privateKey.PubKey().SerializeCompressed()
|
||||
pubKeyAddr, err := util.NewAddressPubKey(serializedKey, activeNetParams.Prefix)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to generate public key address: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
addr := pubKeyAddr.AddressPubKeyHash()
|
||||
fmt.Printf("Public key: %s\n\n", addr)
|
||||
}
|
22
cmd/txgen/addresslist.go
Normal file
22
cmd/txgen/addresslist.go
Normal file
@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
)
|
||||
|
||||
func getAddressList(cfg *config) ([]string, error) {
|
||||
file, err := os.Open(cfg.AddressListPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
addressList := []string{}
|
||||
for scanner.Scan() {
|
||||
addressList = append(addressList, scanner.Text())
|
||||
}
|
||||
|
||||
return addressList, nil
|
||||
}
|
34
cmd/txgen/config.go
Normal file
34
cmd/txgen/config.go
Normal file
@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/jessevdk/go-flags"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
AddressListPath string `long:"addresslist" description:"Path to a list of nodes' JSON-RPC endpoints" required:"true"`
|
||||
PrivateKey string `long:"private-key" description:"Private key" required:"true"`
|
||||
CertificatePath string `long:"cert" description:"Path to certificate accepted by JSON-RPC endpoint"`
|
||||
DisableTLS bool `long:"notls" description:"Disable TLS"`
|
||||
}
|
||||
|
||||
func parseConfig() (*config, error) {
|
||||
cfg := &config{}
|
||||
parser := flags.NewParser(cfg, flags.PrintErrors|flags.HelpFlag)
|
||||
_, err := parser.Parse()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cfg.CertificatePath == "" && !cfg.DisableTLS {
|
||||
return nil, errors.New("--notls has to be disabled if --cert is used")
|
||||
}
|
||||
|
||||
if cfg.CertificatePath != "" && cfg.DisableTLS {
|
||||
return nil, errors.New("--cert should be omitted if --notls is used")
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
47
cmd/txgen/connect.go
Normal file
47
cmd/txgen/connect.go
Normal file
@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/daglabs/btcd/rpcclient"
|
||||
)
|
||||
|
||||
func connectToServers(cfg *config, addressList []string) ([]*rpcclient.Client, error) {
|
||||
clients := make([]*rpcclient.Client, len(addressList))
|
||||
|
||||
var cert []byte
|
||||
if !cfg.DisableTLS {
|
||||
var err error
|
||||
cert, err = ioutil.ReadFile(cfg.CertificatePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error reading certificates file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
for i, address := range addressList {
|
||||
connCfg := &rpcclient.ConnConfig{
|
||||
Host: address,
|
||||
Endpoint: "ws",
|
||||
User: "user",
|
||||
Pass: "pass",
|
||||
DisableTLS: cfg.DisableTLS,
|
||||
}
|
||||
|
||||
if !cfg.DisableTLS {
|
||||
connCfg.Certificates = cert
|
||||
}
|
||||
|
||||
client, err := rpcclient.New(connCfg, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error connecting to address %s: %s", address, err)
|
||||
}
|
||||
|
||||
clients[i] = client
|
||||
|
||||
log.Printf("Connected to server %s", address)
|
||||
}
|
||||
|
||||
return clients, nil
|
||||
}
|
33
cmd/txgen/docker/Dockerfile
Normal file
33
cmd/txgen/docker/Dockerfile
Normal file
@ -0,0 +1,33 @@
|
||||
# -- multistage docker build: stage #1: build stage
|
||||
FROM golang:1.12-alpine AS build
|
||||
|
||||
RUN mkdir -p /go/src/github.com/daglabs/btcd
|
||||
|
||||
WORKDIR /go/src/github.com/daglabs/btcd
|
||||
|
||||
RUN apk add --no-cache curl git
|
||||
|
||||
# GO111MODULE=on forces Go to use the go-module system
|
||||
# TODO: remove this once Go 1.13 is released
|
||||
ENV GO111MODULE=on
|
||||
|
||||
COPY go.mod .
|
||||
COPY go.sum .
|
||||
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN cd cmd/txgen && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o txgen .
|
||||
|
||||
# --- multistage docker build: stage #2: runtime image
|
||||
FROM alpine
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache tini
|
||||
|
||||
COPY --from=build /go/src/github.com/daglabs/btcd/cmd/txgen/txgen /app/
|
||||
|
||||
ENTRYPOINT ["/sbin/tini", "--"]
|
||||
|
||||
CMD ["/app/txgen"]
|
9
cmd/txgen/docker/README
Normal file
9
cmd/txgen/docker/README
Normal file
@ -0,0 +1,9 @@
|
||||
1. To build docker image invoke following command from btcd root directory:
|
||||
docker build -t txgen -f ./cmd/txgen/docker/Dockerfile .
|
||||
|
||||
2. To run:
|
||||
a. create folder ~/.btcd/txgen with the following files:
|
||||
rpc.cert - certificate file that all rpc nodes accept
|
||||
addresses - list of node addresses in the format [hostname]:[port]. One node per line
|
||||
b. run:
|
||||
docker run -v ~/.btcd:/root/.btcd -t txgen
|
85
cmd/txgen/main.go
Normal file
85
cmd/txgen/main.go
Normal file
@ -0,0 +1,85 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runtime/debug"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/daglabs/btcd/btcec"
|
||||
"github.com/daglabs/btcd/dagconfig"
|
||||
"github.com/daglabs/btcd/rpcclient"
|
||||
"github.com/daglabs/btcd/signal"
|
||||
"github.com/daglabs/btcd/util"
|
||||
"github.com/daglabs/btcd/util/base58"
|
||||
)
|
||||
|
||||
var (
|
||||
isRunning int32
|
||||
activeNetParams *dagconfig.Params = &dagconfig.DevNetParams
|
||||
p2pkhAddress util.Address
|
||||
privateKey *btcec.PrivateKey
|
||||
)
|
||||
|
||||
// privateKeyToP2pkhAddress generates p2pkh address from private key.
|
||||
func privateKeyToP2pkhAddress(key *btcec.PrivateKey, net *dagconfig.Params) (util.Address, error) {
|
||||
serializedKey := key.PubKey().SerializeCompressed()
|
||||
pubKeyAddr, err := util.NewAddressPubKey(serializedKey, net.Prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pubKeyAddr.AddressPubKeyHash(), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer handlePanic()
|
||||
|
||||
cfg, err := parseConfig()
|
||||
if err != nil {
|
||||
log.Panicf("Error parsing command-line arguments: %s", err)
|
||||
}
|
||||
|
||||
privateKeyBytes := base58.Decode(cfg.PrivateKey)
|
||||
privateKey, _ = btcec.PrivKeyFromBytes(btcec.S256(), privateKeyBytes)
|
||||
|
||||
p2pkhAddress, err = privateKeyToP2pkhAddress(privateKey, activeNetParams)
|
||||
if err != nil {
|
||||
log.Panicf("Failed to get P2PKH address from private key: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("P2PKH address for private key: %s\n", p2pkhAddress)
|
||||
|
||||
addressList, err := getAddressList(cfg)
|
||||
if err != nil {
|
||||
log.Panicf("Couldn't load address list: %s", err)
|
||||
}
|
||||
|
||||
clients, err := connectToServers(cfg, addressList)
|
||||
if err != nil {
|
||||
log.Panicf("Error connecting to servers: %s", err)
|
||||
}
|
||||
defer disconnect(clients)
|
||||
|
||||
atomic.StoreInt32(&isRunning, 1)
|
||||
|
||||
go txLoop(clients)
|
||||
|
||||
interrupt := signal.InterruptListener()
|
||||
<-interrupt
|
||||
|
||||
atomic.StoreInt32(&isRunning, 0)
|
||||
}
|
||||
|
||||
func disconnect(clients []*rpcclient.Client) {
|
||||
log.Printf("Disconnecting clients")
|
||||
for _, client := range clients {
|
||||
client.Disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
func handlePanic() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
log.Printf("Fatal error: %s", err)
|
||||
log.Printf("Stack trace: %s", debug.Stack())
|
||||
}
|
||||
}
|
320
cmd/txgen/txloop.go
Normal file
320
cmd/txgen/txloop.go
Normal file
@ -0,0 +1,320 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/daglabs/btcd/dagconfig/daghash"
|
||||
"github.com/daglabs/btcd/mempool"
|
||||
"github.com/daglabs/btcd/rpcclient"
|
||||
"github.com/daglabs/btcd/txscript"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
)
|
||||
|
||||
// utxo represents an unspent output spendable by the memWallet. The maturity
|
||||
// height of the transaction is recorded in order to properly observe the
|
||||
// maturity period of direct coinbase outputs.
|
||||
type utxo struct {
|
||||
txOut *wire.TxOut
|
||||
isLocked bool
|
||||
}
|
||||
|
||||
var (
|
||||
random = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
utxos map[wire.OutPoint]*utxo
|
||||
pkScript []byte
|
||||
spentTxs map[daghash.TxID]bool
|
||||
)
|
||||
|
||||
const (
|
||||
// Those constants should be updated, when monetary policy changed
|
||||
minSpendableAmount uint64 = 10000
|
||||
minRelayTxFee uint64 = uint64(mempool.DefaultMinRelayTxFee)
|
||||
)
|
||||
|
||||
func isDust(value uint64) bool {
|
||||
return value < minSpendableAmount+minRelayTxFee
|
||||
}
|
||||
|
||||
// evalOutputs evaluates each of the passed outputs, creating a new matching
|
||||
// utxo within the wallet if we're able to spend the output.
|
||||
func evalOutputs(outputs []*wire.TxOut, txID *daghash.TxID) {
|
||||
for i, output := range outputs {
|
||||
if isDust(output.Value) {
|
||||
continue
|
||||
}
|
||||
op := wire.OutPoint{TxID: *txID, Index: uint32(i)}
|
||||
utxos[op] = &utxo{txOut: output}
|
||||
}
|
||||
}
|
||||
|
||||
// evalInputs scans all the passed inputs, deleting any utxos within the
|
||||
// wallet which are spent by an input.
|
||||
func evalInputs(inputs []*wire.TxIn) {
|
||||
for _, txIn := range inputs {
|
||||
op := txIn.PreviousOutPoint
|
||||
if _, ok := utxos[op]; ok {
|
||||
delete(utxos, op)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func utxosFunds() uint64 {
|
||||
var funds uint64
|
||||
for _, utxo := range utxos {
|
||||
if utxo.isLocked {
|
||||
continue
|
||||
}
|
||||
funds += utxo.txOut.Value
|
||||
}
|
||||
return funds
|
||||
}
|
||||
|
||||
func isTxMatured(tx *wire.MsgTx, confirmations uint64) bool {
|
||||
if !tx.IsBlockReward() {
|
||||
return confirmations >= 1
|
||||
}
|
||||
return confirmations >= uint64(float64(activeNetParams.BlockRewardMaturity)*1.5)
|
||||
}
|
||||
|
||||
func fetchAndPopulateUtxos(client *rpcclient.Client) (funds uint64, exit bool, err error) {
|
||||
skipCount := 0
|
||||
for atomic.LoadInt32(&isRunning) == 1 {
|
||||
arr, err := client.SearchRawTransactionsVerbose(p2pkhAddress, skipCount, 1000, true, false, nil)
|
||||
if err != nil {
|
||||
log.Printf("No spandable transactions found and SearchRawTransactionsVerbose failed: %s", err)
|
||||
funds := utxosFunds()
|
||||
if !isDust(funds) {
|
||||
// we have something to spend
|
||||
log.Printf("We have enough funds to generate transactions: %d", funds)
|
||||
return funds, false, nil
|
||||
}
|
||||
log.Printf("Sleeping 30 sec...")
|
||||
for i := 0; i < 30; i++ {
|
||||
time.Sleep(time.Second)
|
||||
if atomic.LoadInt32(&isRunning) != 1 {
|
||||
return 0, true, nil
|
||||
}
|
||||
}
|
||||
skipCount = 0
|
||||
continue
|
||||
}
|
||||
receivedCount := len(arr)
|
||||
skipCount += receivedCount
|
||||
log.Printf("Received %d transactions", receivedCount)
|
||||
for _, searchResult := range arr {
|
||||
txBytes, err := hex.DecodeString(searchResult.Hex)
|
||||
if err != nil {
|
||||
log.Printf("Failed to decode transactions bytes: %s", err)
|
||||
continue
|
||||
}
|
||||
txID, err := daghash.NewTxIDFromStr(searchResult.TxID)
|
||||
if err != nil {
|
||||
log.Printf("Failed to decode transaction ID: %s", err)
|
||||
continue
|
||||
}
|
||||
var tx wire.MsgTx
|
||||
rbuf := bytes.NewReader(txBytes)
|
||||
err = tx.Deserialize(rbuf)
|
||||
if err != nil {
|
||||
log.Printf("Failed to deserialize transaction: %s", err)
|
||||
continue
|
||||
}
|
||||
if spentTxs[*txID] {
|
||||
continue
|
||||
}
|
||||
if isTxMatured(&tx, searchResult.Confirmations) {
|
||||
spentTxs[*txID] = true
|
||||
evalOutputs(tx.TxOut, txID)
|
||||
evalInputs(tx.TxIn)
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, true, nil
|
||||
}
|
||||
|
||||
// fundTx attempts to fund a transaction sending amount bitcoin. The coins are
|
||||
// selected such that the final amount spent pays enough fees as dictated by
|
||||
// the passed fee rate. The passed fee rate should be expressed in
|
||||
// satoshis-per-byte.
|
||||
func fundTx(tx *wire.MsgTx, amount uint64, feeRate uint64) (uint64, error) {
|
||||
const (
|
||||
// spendSize is the largest number of bytes of a sigScript
|
||||
// which spends a p2pkh output: OP_DATA_73 <sig> OP_DATA_33 <pubkey>
|
||||
spendSize = 1 + 73 + 1 + 33
|
||||
)
|
||||
|
||||
var (
|
||||
amountSelected uint64
|
||||
txSize int
|
||||
)
|
||||
|
||||
for outPoint, utxo := range utxos {
|
||||
if utxo.isLocked {
|
||||
continue
|
||||
}
|
||||
|
||||
amountSelected += utxo.txOut.Value
|
||||
|
||||
// Add the selected output to the transaction, updating the
|
||||
// current tx size while accounting for the size of the future
|
||||
// sigScript.
|
||||
tx.AddTxIn(wire.NewTxIn(&outPoint, nil))
|
||||
txSize = tx.SerializeSize() + spendSize*len(tx.TxIn)
|
||||
|
||||
// Calculate the fee required for the txn at this point
|
||||
// observing the specified fee rate. If we don't have enough
|
||||
// coins from he current amount selected to pay the fee, then
|
||||
// continue to grab more coins.
|
||||
reqFee := uint64(txSize) * feeRate
|
||||
if amountSelected-reqFee < amount {
|
||||
continue
|
||||
}
|
||||
|
||||
// If we have any change left over, then add an additional
|
||||
// output to the transaction reserved for change.
|
||||
changeVal := amountSelected - amount - reqFee
|
||||
if changeVal > 0 {
|
||||
changeOutput := &wire.TxOut{
|
||||
Value: changeVal,
|
||||
PkScript: pkScript,
|
||||
}
|
||||
tx.AddTxOut(changeOutput)
|
||||
}
|
||||
|
||||
return reqFee, nil
|
||||
}
|
||||
|
||||
// If we've reached this point, then coin selection failed due to an
|
||||
// insufficient amount of coins.
|
||||
return 0, fmt.Errorf("not enough funds for coin selection")
|
||||
}
|
||||
|
||||
// signTxAndLockSpentUtxo signs new transaction and locks spentutxo
|
||||
func signTxAndLockSpentUtxo(tx *wire.MsgTx) error {
|
||||
// Populate all the selected inputs with valid sigScript for spending.
|
||||
// Along the way record all outputs being spent in order to avoid a
|
||||
// potential double spend.
|
||||
spentOutputs := make([]*utxo, 0, len(tx.TxIn))
|
||||
for i, txIn := range tx.TxIn {
|
||||
outPoint := txIn.PreviousOutPoint
|
||||
utxo := utxos[outPoint]
|
||||
txOut := utxo.txOut
|
||||
|
||||
sigScript, err := txscript.SignatureScript(tx, i, txOut.PkScript,
|
||||
txscript.SigHashAll, privateKey, true)
|
||||
if err != nil {
|
||||
log.Printf("Failed to sign transaction: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
txIn.SignatureScript = sigScript
|
||||
|
||||
spentOutputs = append(spentOutputs, utxo)
|
||||
}
|
||||
|
||||
// As these outputs are now being spent by this newly created
|
||||
// transaction, mark the outputs are "locked". This action ensures
|
||||
// these outputs won't be double spent by any subsequent transactions.
|
||||
// These locked outputs can be freed via a call to UnlockOutputs.
|
||||
for _, utxo := range spentOutputs {
|
||||
utxo.isLocked = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createTransaction returns a fully signed transaction paying to the specified
|
||||
// outputs while observing the desired fee rate. The passed fee rate should be
|
||||
// expressed in satoshis-per-byte.
|
||||
func createTransaction(outputs []*wire.TxOut, feeRate uint64) (*wire.MsgTx, uint64, error) {
|
||||
tx := wire.NewNativeMsgTx(wire.TxVersion, nil, nil)
|
||||
|
||||
// Tally up the total amount to be sent in order to perform coin
|
||||
// selection shortly below.
|
||||
var outputAmount uint64
|
||||
for _, output := range outputs {
|
||||
outputAmount += output.Value
|
||||
tx.AddTxOut(output)
|
||||
}
|
||||
|
||||
// Attempt to fund the transaction with spendable utxos.
|
||||
fees, err := fundTx(tx, outputAmount, feeRate)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
err = signTxAndLockSpentUtxo(tx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return tx, fees, nil
|
||||
}
|
||||
|
||||
// txLoop performs main loop of transaction generation
|
||||
func txLoop(clients []*rpcclient.Client) {
|
||||
clientsCount := int64(len(clients))
|
||||
|
||||
utxos = make(map[wire.OutPoint]*utxo)
|
||||
spentTxs = make(map[daghash.TxID]bool)
|
||||
|
||||
pkScript, err := txscript.PayToAddrScript(p2pkhAddress)
|
||||
if err != nil {
|
||||
log.Printf("Failed to generate pkscript to address: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
for atomic.LoadInt32(&isRunning) == 1 {
|
||||
funds, exit, err := fetchAndPopulateUtxos(clients[0])
|
||||
if exit {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("fetchAndPopulateUtxos failed: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if isDust(funds) {
|
||||
log.Printf("fetchAndPopulateUtxos returned not enough funds")
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("UTXO funds after population %d", funds)
|
||||
|
||||
for !isDust(funds) {
|
||||
amount := minSpendableAmount + uint64(random.Int63n(int64(minSpendableAmount*4)))
|
||||
output := wire.NewTxOut(amount, pkScript)
|
||||
|
||||
tx, fees, err := createTransaction([]*wire.TxOut{output}, 10)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed to create transaction (output value %d, funds %d): %s",
|
||||
amount, funds, err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("Created transaction %s: amount %d, fees %d", tx.TxID(), amount, fees)
|
||||
|
||||
funds = utxosFunds()
|
||||
log.Printf("Remaining funds: %d", funds)
|
||||
|
||||
var currentClient *rpcclient.Client
|
||||
if clientsCount == 1 {
|
||||
currentClient = clients[0]
|
||||
} else {
|
||||
currentClient = clients[random.Int63n(clientsCount)]
|
||||
}
|
||||
_, err = currentClient.SendRawTransaction(tx, true)
|
||||
if err != nil {
|
||||
log.Printf("Failed to send transaction: %s", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -73,10 +73,10 @@ type Config struct {
|
||||
// chain tip within the best chain.
|
||||
MedianTimePast func() time.Time
|
||||
|
||||
// CalcSequenceLock defines the function to use in order to generate
|
||||
// CalcSequenceLockNoLock defines the function to use in order to generate
|
||||
// the current sequence lock for the given transaction using the passed
|
||||
// utxo set.
|
||||
CalcSequenceLock func(*util.Tx, blockdag.UTXOSet) (*blockdag.SequenceLock, error)
|
||||
CalcSequenceLockNoLock func(*util.Tx, blockdag.UTXOSet) (*blockdag.SequenceLock, error)
|
||||
|
||||
// IsDeploymentActive returns true if the target deploymentID is
|
||||
// active, and false otherwise. The mempool uses this function to gauge
|
||||
@ -852,7 +852,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
|
||||
// Don't allow the transaction into the mempool unless its sequence
|
||||
// lock is active, meaning that it'll be allowed into the next block
|
||||
// with respect to its defined relative lock times.
|
||||
sequenceLock, err := mp.cfg.CalcSequenceLock(tx, mp.mpUTXOSet)
|
||||
sequenceLock, err := mp.cfg.CalcSequenceLockNoLock(tx, mp.mpUTXOSet)
|
||||
if err != nil {
|
||||
if cerr, ok := err.(blockdag.RuleError); ok {
|
||||
return nil, nil, dagRuleError(cerr)
|
||||
|
@ -319,12 +319,12 @@ func newPoolHarness(dagParams *dagconfig.Params, numOutputs uint32, dbName strin
|
||||
MinRelayTxFee: 1000, // 1 Satoshi per byte
|
||||
MaxTxVersion: 1,
|
||||
},
|
||||
DAGParams: dagParams,
|
||||
BestHeight: chain.BestHeight,
|
||||
MedianTimePast: chain.MedianTimePast,
|
||||
CalcSequenceLock: calcSequenceLock,
|
||||
SigCache: nil,
|
||||
AddrIndex: nil,
|
||||
DAGParams: dagParams,
|
||||
BestHeight: chain.BestHeight,
|
||||
MedianTimePast: chain.MedianTimePast,
|
||||
CalcSequenceLockNoLock: calcSequenceLock,
|
||||
SigCache: nil,
|
||||
AddrIndex: nil,
|
||||
}),
|
||||
}
|
||||
|
||||
@ -681,7 +681,7 @@ func TestProcessTransaction(t *testing.T) {
|
||||
}
|
||||
|
||||
//Checks that transactions get rejected from mempool if sequence lock is not active
|
||||
harness.txPool.cfg.CalcSequenceLock = func(tx *util.Tx,
|
||||
harness.txPool.cfg.CalcSequenceLockNoLock = func(tx *util.Tx,
|
||||
view blockdag.UTXOSet) (*blockdag.SequenceLock, error) {
|
||||
|
||||
return &blockdag.SequenceLock{
|
||||
@ -704,7 +704,7 @@ func TestProcessTransaction(t *testing.T) {
|
||||
if err.Error() != expectedErrStr {
|
||||
t.Errorf("Unexpected error message. Expected \"%s\" but got \"%s\"", expectedErrStr, err.Error())
|
||||
}
|
||||
harness.txPool.cfg.CalcSequenceLock = calcSequenceLock
|
||||
harness.txPool.cfg.CalcSequenceLockNoLock = calcSequenceLock
|
||||
|
||||
// This is done in order to increase the input age, so the tx priority will be higher
|
||||
harness.chain.SetHeight(curHeight + 100)
|
||||
|
@ -406,8 +406,7 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTe
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
coinbaseTx, err := CreateCoinbaseTx(g.dagParams, coinbaseScript,
|
||||
nextBlockHeight, payToAddress)
|
||||
coinbaseTx, err := CreateCoinbaseTx(g.dagParams, coinbaseScript, nextBlockHeight, payToAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -630,7 +629,6 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTe
|
||||
// the total fees accordingly.
|
||||
blockSize -= wire.MaxVarIntPayload -
|
||||
uint32(wire.VarIntSerializeSize(uint64(len(blockTxns))))
|
||||
coinbaseTx.MsgTx().TxOut[0].Value += totalFees
|
||||
|
||||
// Calculate the required difficulty for the block. The timestamp
|
||||
// is potentially adjusted to ensure it comes after the median time of
|
||||
|
@ -2476,8 +2476,8 @@ func NewServer(listenAddrs []string, db database.DB, dagParams *dagconfig.Params
|
||||
DAGParams: dagParams,
|
||||
BestHeight: func() int32 { return s.DAG.Height() }, //TODO: (Ori) This is probably wrong. Done only for compilation
|
||||
MedianTimePast: func() time.Time { return s.DAG.CalcPastMedianTime() },
|
||||
CalcSequenceLock: func(tx *util.Tx, utxoSet blockdag.UTXOSet) (*blockdag.SequenceLock, error) {
|
||||
return s.DAG.CalcSequenceLock(tx, utxoSet, true)
|
||||
CalcSequenceLockNoLock: func(tx *util.Tx, utxoSet blockdag.UTXOSet) (*blockdag.SequenceLock, error) {
|
||||
return s.DAG.CalcSequenceLockNoLock(tx, utxoSet, true)
|
||||
},
|
||||
IsDeploymentActive: s.DAG.IsDeploymentActive,
|
||||
SigCache: s.SigCache,
|
||||
|
Loading…
x
Reference in New Issue
Block a user