Skip to content

Commit

Permalink
feat: Mine block
Browse files Browse the repository at this point in the history
  • Loading branch information
humblenginr committed Apr 10, 2024
1 parent 252fb95 commit 3accb08
Show file tree
Hide file tree
Showing 15 changed files with 733 additions and 133 deletions.
19 changes: 13 additions & 6 deletions SOLUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ Programming language to use: Golang
### Validation

There are different types of transactions:
1. p2pkh (pay to public key hash)
2. p2sh (pay to script hash)
3. p2wpkh (pay to script hash)
1. p2pkh (pay to public key hash) [x]
2. p2sh (pay to script hash) []
3. p2wpkh (pay to script hash) [x]
4. p2wsh (pay to witness script hash)
5. p2tr
5. p2tr [x]

What about multisigs?
First we will only take these things into account. If a transaction any other type than this, then we will log it and see what it is.
Expand Down Expand Up @@ -92,12 +92,19 @@ INPUT:

We will start with these validations, and run the tests, and then based on the results we can add more rules.


### Assigning fee/size
Here we basically have to calculate the transaction fees for the given transaction, and then store them in a map that has transaction
id as the key and the (fee/size) as the value.
What do we keep as the size? Serialized transaction size(with or without witness?)


Let us first mine a block and see if our valid txns are actually valid
## Coinbase transaction

## Mining a block
We need to do the following things:
1. Select a list of transactions to put in the block
2. Create a candidate block
3. Create coinbase transaction
4. Calculate the Merkle root
5. Find the nonce

5 changes: 4 additions & 1 deletion run.sh
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
# Update this file to run your own code
# Update this file to run your own code
cd src
go mod tidy
go run main.go
Binary file added src/.DS_Store
Binary file not shown.
84 changes: 53 additions & 31 deletions src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,68 @@ package main
import (
"encoding/json"
"fmt"
"io"
"io/fs"
"os"

"github.com/humblenginr/btc-miner/mining"
txn "github.com/humblenginr/btc-miner/transaction"
"github.com/humblenginr/btc-miner/validation"
"github.com/humblenginr/btc-miner/utils"
"github.com/humblenginr/btc-miner/validation"
)

var (
MempoolDirPath = "../mempool"
ValidTxnsDirPath = "../valid-txns"
OutputFilePath = "../output.txt"
ValidTxnsDirPerm = 0755
)

func main() {
// p2pkh
fileContent, err := os.Open("../mempool/ff907975dc0cfa299e908e5fba6df56c764866d9a9c22828824c28b8e4511320.json")
// p2wpkh
fileContent, err = os.Open("../mempool/ff9c6d05b875c29adb975d4d3c80977bc2f8371be90b71a185da90127ffd37f3.json")
fileContent, err = os.Open("../mempool/feb678eaf0f8326c6f34e47953afe7244eac7a1ebe7e55ebdb6bf1ccb0d2aaae.json")
fileContent, err = os.Open("../mempool/ff45041e1dacbe980606470f65a3d2b454347d28415cae0d87915126f17bfdd2.json")
// p2tr - key path spending
fileContent, err = os.Open("../mempool/00d9c01fd8722f63cc327c93e59de64395d1e6ca5861ae6b9b149b364d082352.json")
fileContent, err = os.Open("../mempool/02e09abed1c49fa18819425c9fde49b3dcfcc9a2652fee7c8c3e15fd7f140fa3.json")
// p2tr - script path spending
fileContent, err = os.Open("../mempool/feb1fe7a84a3f56e2f1b761855b20082a012d55c50f4d5fcbfc9478ad8fcb9fe.json")
fileContent, err = os.Open("../mempool/faa72f786352755f8cf783f03b062c48c13f35b22b67acb658dfaeb12ecd189f.json")
fileContent, err = os.Open("../mempool/fd72b15b25fd18f95bcf35aab35f2b51056f4b39cb79f2bae06551c7e6951430.json")
fileContent, err = os.Open("../mempool/fac725231f08430c92a0542d39932aa91a26d857386bfb12cbb49899d753f3a9.json")
func Validate(tx txn.Transaction) bool {
for inputIdx := range tx.Vin {
if(!validation.Validate(tx, inputIdx)){
return false
}
}
return true
}

if err != nil {
panic(err)
}
defer fileContent.Close()
byteResult, _ := io.ReadAll(fileContent)
var transaction txn.Transaction
err = json.Unmarshal(byteResult, &transaction)
func LogDetailsAboutTx(tx txn.Transaction){
txid := tx.TxHash()
rev := utils.ReverseBytes(txid)
fmt.Printf("Tx hex: %x\n",tx.RawHex() )
fmt.Printf("Txid: %x\n", txid)
fmt.Printf("Filename: %x\n", utils.Hash(rev))
}

func UpdateValidTxns() {
os.RemoveAll(ValidTxnsDirPath)
files, err := os.ReadDir(MempoolDirPath)
if err != nil {
panic(err)
}
txid := transaction.TxHash()
rev := utils.ReverseBytes(txid)
fmt.Printf("Tx hex: %x\n",transaction.RawHex() )
fmt.Printf("Txid should be: %x\n", txid)
fmt.Printf("Filename should be: %x\n", utils.Hash(rev))
ret := validation.Validate(transaction, 0)
fmt.Printf("Is Valid: %v\n", ret)
var transaction txn.Transaction
for _, f := range files {
txnPath := fmt.Sprintf("%s/%s", MempoolDirPath, f.Name())
byteResult, _ := os.ReadFile(txnPath)
err = json.Unmarshal(byteResult, &transaction)
if err != nil {
panic(err)
}
isValid := Validate(transaction)
err = os.MkdirAll(ValidTxnsDirPath, fs.FileMode(ValidTxnsDirPerm))
if err != nil {
panic(err)
}
if(isValid){
fileName := fmt.Sprintf("%s/%s", ValidTxnsDirPath, f.Name())
os.WriteFile(fileName, byteResult, fs.FileMode(ValidTxnsDirPerm))
}
}
}

func main() {
// UpdateValidTxns()
txns := mining.SelectTransactionsFromFolder(ValidTxnsDirPath)
candidateBlock := mining.GetCandidateBlock(txns, true)
mining.MineBlock(candidateBlock, OutputFilePath)
}
113 changes: 113 additions & 0 deletions src/mining/block.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package mining

import (
"bufio"
"bytes"
"encoding/binary"
"encoding/hex"
"io"
"os"

txn "github.com/humblenginr/btc-miner/transaction"
)

// Data types taken from: https://developer.bitcoin.org/reference/block_chain.html
type BlockHeader struct {
Version int32 `json:"version"`
PrevBlockHash [32]byte
MerkleRoot [32]byte
// Unix timestamp
Time int64
// Compact representation of difficulty target
Bits uint32
Nonce uint32
}

func (bh *BlockHeader) Serialize(w io.Writer) error {
buf := make([]byte, 4)

binary.LittleEndian.PutUint32(buf[:4], uint32(bh.Version))
if _, err := w.Write(buf[:4]); err != nil {
return err
}

if _, err := w.Write(bh.PrevBlockHash[:]); err != nil {
return err
}

if _, err := w.Write(bh.MerkleRoot[:]); err != nil {
return err
}

binary.LittleEndian.PutUint32(buf[:4], uint32(bh.Time))
if _, err := w.Write(buf[:4]); err != nil {
return err
}

binary.LittleEndian.PutUint32(buf[:4], bh.Bits)
if _, err := w.Write(buf[:4]); err != nil {
return err
}

binary.LittleEndian.PutUint32(buf[:4], bh.Nonce)
if _, err := w.Write(buf[:4]); err != nil {
return err
}

return nil
}

func NewBlockHeader(version int32, prevBlockHash [32]byte, merkleRoot [32]byte, time int64, bits uint32, nonce uint32) BlockHeader {
return BlockHeader{version, prevBlockHash, merkleRoot, time, bits, nonce}
}


type Block struct {
BlockHeader BlockHeader
Coinbase txn.Transaction
Transactions []txn.Transaction
}

func (b *Block) AddTransaction (t txn.Transaction) {
b.Transactions = append(b.Transactions, t)
}

func (b *Block) WriteToFile(filePath string) error {
/*
First line: The block header.
Second line: The serialized coinbase transaction.
Following lines: The transaction IDs (txids) of the transactions mined in the block, in order. The first txid should be that of the coinbase transaction
*/
f, err := os.Create(filePath)
if err != nil {
return err
}
defer f.Close()

w := bufio.NewWriter(f)

// Block header
buf := bytes.NewBuffer(make([]byte, 0, 80))
b.BlockHeader.Serialize(buf)
w.WriteString(hex.EncodeToString(buf.Bytes())+"\n")

// Serialized coinbase transaction
cb := b.Coinbase
w.WriteString(hex.EncodeToString(cb.RawHex())+"\n")

// Txid of coinbase transaction
w.WriteString(hex.EncodeToString(cb.TxHash())+"\n")

// Txid of rest of the transactions
for _, txn := range b.Transactions {
txid := txn.TxHash()
w.WriteString(hex.EncodeToString(txid)+"\n")
}
w.Flush()
return nil
}

func NewBlock(header BlockHeader, coinbase txn.Transaction, txns []txn.Transaction) Block {
return Block{header, coinbase, txns}
}

69 changes: 69 additions & 0 deletions src/mining/coinbase.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package mining

import (
"encoding/hex"

txn "github.com/humblenginr/btc-miner/transaction"
"github.com/humblenginr/btc-miner/utils"
)

var MaxVoutIndex uint32 = 0xffffffff
var WitnessReserveHexString = "0000000000000000000000000000000000000000000000000000000000000000"
// in satoshis
var BlockSubsidy int = 0
var CoinbaseTransactionVersion int32 = 1

func NewCoinbaseTransaction(fees int) txn.Transaction {
var zeroTxid [32]byte
t := txn.Transaction{}
t.Version = CoinbaseTransactionVersion
t.Locktime = 0

vin := txn.Vin{}
vin.IsCoinbase = true
vin.Txid = hex.EncodeToString(zeroTxid[:])
vin.Vout = int(MaxVoutIndex)
// push 3 bytes, 951a06 - which is the block height
vin.ScriptSig = "03951a06"
vin.ScriptSigAsm = "PUSH_3 951a06"
vin.Sequence = 0
t.Vin = append(t.Vin, vin)

vout := txn.Vout{}
// OP_TRUE - anyone can redeem this
vout.ScriptPubKey = "51"
vout.ScriptPubKeyAsm = "OP_TRUE"
vout.Value = BlockSubsidy + fees
t.Vout = append(t.Vout, vout)

return t
}

func AddWitnessCommitment(coinbase *txn.Transaction, txns []*txn.Transaction) error {
var witnessNonce [32]byte

coinbase.Vin[0].Witness = append(coinbase.Vin[0].Witness, WitnessReserveHexString)
witnessMerkleRoot := CalcMerkleRoot(txns, true)

var witnessPreimage [64]byte
copy(witnessPreimage[:32], witnessMerkleRoot[:])
copy(witnessPreimage[32:], witnessNonce[:])
witnessCommitment := utils.DoubleHash(witnessPreimage[:])
witnessScript := []byte{
// OP_RETURN
0x6a,
// OP_DATA36
0x36,
0xaa,
0x21,
0xa9,
0xed,
}
witnessScript = append(witnessScript, witnessCommitment...)
witnessOut := txn.Vout{}
witnessOut.Value = 0
witnessOut.ScriptPubKey = hex.EncodeToString(witnessScript)
coinbase.Vout = append(coinbase.Vout, witnessOut)

return nil
}
Loading

0 comments on commit 3accb08

Please sign in to comment.