2

I'm parsing bitcoin transactions and would like to be able to strip out transaction amounts. This site says that I have to use flags to extract out the transaction amounts but it appears to only take one address into account. Does the same logic hold if I were to apply other flags to the amounts I'm extracting? Also, what are the end markers I could use to extract that amount?

Thank you in advance.

2
  • 1
    I suggest you study some existing code that does transaction parsing, which exists for almost any programming language you'd want. Even if you can't use it directly, it'll teach you more than asking dozens of questions here for every misunderstanding you have. Feb 28, 2023 at 0:50
  • I don't understand your question; what flags are you talking about? The ones in the transaction format to indicate the BIP144 witness serialization? You need to those know whether witnesses are present, but they're unrelated to amounts. Amounts are just 8 byte values, always, without any end markers. I also don't see anything on that page that assumes there is just one address (and even further: addresses don't exist at the protocol level, only scripts/inputs/outputs). Feb 28, 2023 at 0:52

2 Answers 2

3

This site says that I have to use flags to extract out the transaction amounts [...] Also, what are the end markers I could use to extract that amount?

(my emphasis)

My advice is don't search the hex data for magic flag or marker values, parse the data completely:


Here's how I would parse a raw transaction in network format and start to calculate the transaction value:

/* Example basic parsing blockchain transaction data, Redgrittybrick 2023 */
package main

import (
    "bytes"
    "encoding/binary"
    "encoding/hex"
    "fmt"
    "os"
)

const txhex string = 
    "0200000000010179aaafbe7c9d3b0812a489facaf77508c08c190ec7dfd82f12" +
    "9aeb995aca23ab0000000000fdffffff020bd2190000000000160014d2caa7b0" +
    "8db89cd62c9af34da53332d30e53bb1598151b00000000001600143d4427468c" +
    "be7ae396427a1aa9128fa05b18c7db024730440220573fd27574cfdde4843476" +
    "21e1f48f85ae975cb8c2265a04496ded038896822302204a5e04a3a2d160c315" +
    "8caa39b58bfc91ac64c484078ec0225a7d4d2d4454661f012103d96e3819b522" +
    "45e42c76f869c9a875f6ea5344cf1aee2e6b3ab03adcfef0d80ede3b0b00"

func main() {
    txbin, err := hex.DecodeString(txhex)
    check(err)

    buf := bytes.NewBuffer(txbin)

    // VERSION
    version := buf.Next(4)
    fmt.Printf("Version = %x\n", version)

    // OPTIONAL SEGWIT MARKER AND FLAG
    // OR INPUT COUNT
    var segwitMarker, segwitFlag byte // each defaults to 0
    inputCount := NextCInt(buf)
    if inputCount == 0 { // It's a Segwit marker, not an input count
        segwitFlag, err = buf.ReadByte() // expect 1
        check(err)
        fmt.Printf("Segwit Marker = %x, Flag = %x\n" segwitMarker, segwitFlag)
        inputCount = NextCInt(buf)
    }
    fmt.Printf("Input Count = %d\n", inputCount)


    // INPUTS
    for i := 0; i < int(inputCount); i++ {
        txid := buf.Next(32)
        vout := binary.LittleEndian.Uint32(buf.Next(4))
        scriptLen := NextCInt(buf)
        fmt.Printf("Input %d: tx = %x vout = %d\n", i+1, txid, vout)
        fmt.Printf("\tUnlocking Script length = %d\n", scriptLen)
        if scriptLen > 0 {
            script := buf.Next(int(scriptLen))
            fmt.Printf("\tScript = %x\n", script)
        }
    }

    // SEQUENCE
    sequence := buf.Next(4)
    fmt.Printf("Sequence = %x\n", sequence)

    // OUTPUTS
    outCount := NextCInt(buf)
    fmt.Printf("Output count = %d\n", outCount)
    var txValue uint64
    for o := 0; o < int(outCount); o++ {
        amount := binary.LittleEndian.Uint64(buf.Next(8))
        scriptLen := NextCInt(buf)
        script := buf.Next(int(scriptLen))
        fmt.Printf("Output %d: value = %d script = %x\n", o+1, amount, script)
        txValue += amount
    }


    //WITNESSES
    if segwitFlag != 0 {
        for i:=0; i<int(inputCount); i++ {
            compCount := NextCInt(buf)
            for c:=0; c<int(compCount); c++ {
                l := NextCInt(buf)
                if l > 0 {
                    component := buf.Next(int(l))
                    fmt.Printf("Witness %d component %d = %x\n", 
                        i+1, c+1, component)
                }
            }
        }
    }

    // LOCKTIME
    lockTime := buf.Next(4)
    fmt.Printf("Locktime  = %x\n", lockTime)

    // DERIVED DATA
    fmt.Printf("\n(Tx value (less fee) = %d)\n", txValue)
}

// Extracts a Compact Int
func NextCInt(buf *bytes.Buffer) (uint64) {
    var result uint64
    first, err := buf.ReadByte()
    check(err)
    switch first {
    case 0xFF :
        result = binary.LittleEndian.Uint64(buf.Next(8))
    case 0xFE :
        result = uint64(binary.LittleEndian.Uint32(buf.Next(4)))
    case 0xFD :
        result = uint64(binary.LittleEndian.Uint16(buf.Next(2)))
    default :
        result = uint64(first)
    }
    return result
}

// Rudimentary error handling
func check(err error) {
    if err != nil {
        fmt.Fprint(os.Stderr, err)
        os.Exit(1)
    }
}

Output

Version = 02000000
Segwit Marker = 0, Flag = 1
Input Count = 1
Input 1: tx = 79aaafbe7c9d3b0812a489facaf77508c08c190ec7dfd82f129aeb995aca23ab vout = 0
        Unlocking Script length = 0
Sequence = fdffffff
Output count = 2
Output 1: value = 1692171 script = 0014d2caa7b08db89cd62c9af34da53332d30e53bb15
Output 2: value = 1775000 script = 00143d4427468cbe7ae396427a1aa9128fa05b18c7db
Witness 1 component 1 = 30440220573fd27574cfdde484347621e1f48f85ae975cb8c2265a04496ded038896822302204a5e04a3a2d160c3158caa39b58bfc91ac64c484078ec0225a7d4d2d4454661f01
Witness 1 component 2 = 03d96e3819b52245e42c76f869c9a875f6ea5344cf1aee2e6b3ab03adcfef0d80e
Locktime  = de3b0b00

(Tx value (less fee) = 3467171)

The code is quick and dirty. Production code needs more defensive error checking in case of corrupt, invalid or truncated data. For example it should use buf.Read() instead of buf.Next() and check for errors thrown. Normally I'd populate an object (go data struct) instead of just printing stuff out. To calculate the fee you need to extract vout value from referenced prior transactions, fee=sum(ins)-sum(outs).

Note that the code above has no external dependencies - the imports are just standard go libraries that come with the compiler. It doesn't rely on any Bitcoin library (which also means it is unproven code!)

The go compiler is a free download at https://go.dev/ it compiles to a single native execatable (and can cross-compile to other platform), A typical way to use the above might be

  • install go compiler
  • create a project folder somewhere convenient
  • cd to folder
  • copy above go code into file named main.go (or anything.go)
  • go mod init
  • go mod tidy
  • go build
  • run new program

The go mod ... commands are project initialisation and don't need to be repeated when recompiling.

It is certainly more than possible this code has other flaws (comments welcome) but I hope it acts as a potential starting point or at least suggests a possible approach that can be implemented in your preferred programming language.

0
1

It's not clear what method you are using, parsing raw block data or CLI/RPC calls. Since you are only asking for values the basic principle is simple.

Pick a block, and use getblock blockHash 2 but replace blockHash with actual hash. The 2 will return details on each transaction in the block as if you did a getrawtransaction on every transaction.

Now all you need to do is add up all the output values for each transaction.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.