github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/tests/block_test_util.go (about)

     1  // Copyright 2015 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package tests
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/hex"
    22  	"fmt"
    23  	"io"
    24  	"math/big"
    25  	"runtime"
    26  	"strconv"
    27  	"strings"
    28  
    29  	"github.com/ethereumproject/ethash"
    30  	"github.com/ethereumproject/go-ethereum/common"
    31  	"github.com/ethereumproject/go-ethereum/core"
    32  	"github.com/ethereumproject/go-ethereum/core/state"
    33  	"github.com/ethereumproject/go-ethereum/core/types"
    34  	"github.com/ethereumproject/go-ethereum/crypto"
    35  	"github.com/ethereumproject/go-ethereum/ethdb"
    36  	"github.com/ethereumproject/go-ethereum/event"
    37  	"github.com/ethereumproject/go-ethereum/logger/glog"
    38  	"github.com/ethereumproject/go-ethereum/rlp"
    39  )
    40  
    41  // Block Test JSON Format
    42  type BlockTest struct {
    43  	Genesis *types.Block
    44  
    45  	Json          *btJSON
    46  	preAccounts   map[string]btAccount
    47  	postAccounts  map[string]btAccount
    48  	lastblockhash string
    49  }
    50  
    51  type btJSON struct {
    52  	Blocks             []btBlock
    53  	GenesisBlockHeader btHeader
    54  	Pre                map[string]btAccount
    55  	PostState          map[string]btAccount
    56  	Lastblockhash      string
    57  }
    58  
    59  type btBlock struct {
    60  	BlockHeader  *btHeader
    61  	Rlp          string
    62  	Transactions []btTransaction
    63  	UncleHeaders []*btHeader
    64  }
    65  
    66  type btAccount struct {
    67  	Balance    string
    68  	Code       string
    69  	Nonce      string
    70  	Storage    map[string]string
    71  	PrivateKey string
    72  }
    73  
    74  type btHeader struct {
    75  	Bloom            string
    76  	Coinbase         string
    77  	MixHash          string
    78  	Nonce            string
    79  	Number           string
    80  	Hash             string
    81  	ParentHash       string
    82  	ReceiptTrie      string
    83  	SeedHash         string
    84  	StateRoot        string
    85  	TransactionsTrie string
    86  	UncleHash        string
    87  
    88  	ExtraData  string
    89  	Difficulty string
    90  	GasLimit   string
    91  	GasUsed    string
    92  	Timestamp  string
    93  }
    94  
    95  type btTransaction struct {
    96  	Data     string
    97  	GasLimit string
    98  	GasPrice string
    99  	Nonce    string
   100  	R        string
   101  	S        string
   102  	To       string
   103  	V        string
   104  	Value    string
   105  }
   106  
   107  func RunBlockTestWithReader(homesteadBlock, gasPriceFork *big.Int, r io.Reader, skipTests []string) error {
   108  	btjs := make(map[string]*btJSON)
   109  	if err := readJson(r, &btjs); err != nil {
   110  		return err
   111  	}
   112  
   113  	bt, err := convertBlockTests(btjs)
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	if err := runBlockTests(homesteadBlock, gasPriceFork, bt, skipTests); err != nil {
   119  		return err
   120  	}
   121  	return nil
   122  }
   123  
   124  func RunBlockTest(homesteadBlock, gasPriceFork *big.Int, file string, skipTests []string) error {
   125  	btjs := make(map[string]*btJSON)
   126  	if err := readJsonFile(file, &btjs); err != nil {
   127  		return err
   128  	}
   129  
   130  	bt, err := convertBlockTests(btjs)
   131  	if err != nil {
   132  		return err
   133  	}
   134  	if err := runBlockTests(homesteadBlock, gasPriceFork, bt, skipTests); err != nil {
   135  		return err
   136  	}
   137  	return nil
   138  }
   139  
   140  func runBlockTests(homesteadBlock, gasPriceFork *big.Int, bt map[string]*BlockTest, skipTests []string) error {
   141  	skipTest := make(map[string]bool, len(skipTests))
   142  	for _, name := range skipTests {
   143  		skipTest[name] = true
   144  	}
   145  
   146  	for name, test := range bt {
   147  		if skipTest[name] {
   148  			glog.Infoln("Skipping block test", name)
   149  			continue
   150  		}
   151  		// test the block
   152  		if err := runBlockTest(homesteadBlock, gasPriceFork, test); err != nil {
   153  			return fmt.Errorf("%s: %v", name, err)
   154  		}
   155  		glog.Infoln("Block test passed: ", name)
   156  
   157  	}
   158  	return nil
   159  }
   160  
   161  func runBlockTest(homesteadBlock, gasPriceFork *big.Int, test *BlockTest) error {
   162  	// import pre accounts & construct test genesis block & state root
   163  	db, _ := ethdb.NewMemDatabase()
   164  	if _, err := test.InsertPreState(db); err != nil {
   165  		return fmt.Errorf("InsertPreState: %v", err)
   166  	}
   167  
   168  	core.WriteTd(db, test.Genesis.Hash(), test.Genesis.Difficulty())
   169  	core.WriteBlock(db, test.Genesis)
   170  	core.WriteCanonicalHash(db, test.Genesis.Hash(), test.Genesis.NumberU64())
   171  	core.WriteHeadBlockHash(db, test.Genesis.Hash())
   172  	evmux := new(event.TypeMux)
   173  
   174  	core.DefaultConfigMainnet.ChainConfig.ForkByName("Homestead").Block = homesteadBlock
   175  	if gasPriceFork != nil {
   176  		core.DefaultConfigMainnet.ChainConfig.ForkByName("GasReprice").Block = gasPriceFork
   177  	}
   178  
   179  	chain, err := core.NewBlockChain(db, core.DefaultConfigMainnet.ChainConfig, ethash.NewShared(), evmux)
   180  	if err != nil {
   181  		return err
   182  	}
   183  
   184  	//vm.Debug = true
   185  	validBlocks, err := test.TryBlocksInsert(chain)
   186  	if err != nil {
   187  		return err
   188  	}
   189  
   190  	lastblockhash := common.HexToHash(test.lastblockhash)
   191  	cmlast := chain.LastBlockHash()
   192  	if lastblockhash != cmlast {
   193  		return fmt.Errorf("lastblockhash validation mismatch: want: %x, have: %x", lastblockhash, cmlast)
   194  	}
   195  
   196  	newDB, err := chain.State()
   197  	if err != nil {
   198  		return err
   199  	}
   200  	if err = test.ValidatePostState(newDB); err != nil {
   201  		return fmt.Errorf("post state validation failed: %v", err)
   202  	}
   203  
   204  	return test.ValidateImportedHeaders(chain, validBlocks)
   205  }
   206  
   207  // InsertPreState populates the given database with the genesis
   208  // accounts defined by the test.
   209  func (t *BlockTest) InsertPreState(db ethdb.Database) (*state.StateDB, error) {
   210  	statedb, err := state.New(common.Hash{}, state.NewDatabase(db))
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  	for addrString, acct := range t.preAccounts {
   215  		code, err := hex.DecodeString(strings.TrimPrefix(acct.Code, "0x"))
   216  		if err != nil {
   217  			return nil, err
   218  		}
   219  		balance, ok := new(big.Int).SetString(acct.Balance, 0)
   220  		if !ok {
   221  			return nil, err
   222  		}
   223  		nonce, err := strconv.ParseUint(prepInt(16, acct.Nonce), 16, 64)
   224  		if err != nil {
   225  			return nil, err
   226  		}
   227  		obj := statedb.CreateAccount(common.HexToAddress(addrString))
   228  		obj.SetCode(crypto.Keccak256Hash(code), code)
   229  		obj.SetBalance(balance)
   230  		obj.SetNonce(nonce)
   231  		for k, v := range acct.Storage {
   232  			statedb.SetState(common.HexToAddress(addrString), common.HexToHash(k), common.HexToHash(v))
   233  		}
   234  	}
   235  
   236  	root, err := statedb.CommitTo(db, false)
   237  	if err != nil {
   238  		return nil, fmt.Errorf("error writing state: %v", err)
   239  	}
   240  	if t.Genesis.Root() != root {
   241  		return nil, fmt.Errorf("computed state root does not match genesis block: genesis=%x computed=%x", t.Genesis.Root().Bytes()[:4], root.Bytes()[:4])
   242  	}
   243  	return statedb, nil
   244  }
   245  
   246  /* See https://github.com/ethereum/tests/wiki/Blockchain-Tests-II
   247  
   248     Whether a block is valid or not is a bit subtle, it's defined by presence of
   249     blockHeader, transactions and uncleHeaders fields. If they are missing, the block is
   250     invalid and we must verify that we do not accept it.
   251  
   252     Since some tests mix valid and invalid blocks we need to check this for every block.
   253  
   254     If a block is invalid it does not necessarily fail the test, if it's invalidness is
   255     expected we are expected to ignore it and continue processing and then validate the
   256     post state.
   257  */
   258  func (t *BlockTest) TryBlocksInsert(blockchain *core.BlockChain) ([]btBlock, error) {
   259  	validBlocks := make([]btBlock, 0)
   260  	// insert the test blocks, which will execute all transactions
   261  	for _, b := range t.Json.Blocks {
   262  		cb, err := mustConvertBlock(b)
   263  		if err != nil {
   264  			if b.BlockHeader == nil {
   265  				continue // OK - block is supposed to be invalid, continue with next block
   266  			} else {
   267  				return nil, fmt.Errorf("Block RLP decoding failed when expected to succeed: %v", err)
   268  			}
   269  		}
   270  
   271  		// RLP decoding worked, try to insert into chain:
   272  		blocks := types.Blocks{cb}
   273  		if res := blockchain.InsertChain(blocks); res.Error != nil {
   274  			if b.BlockHeader == nil {
   275  				// block supposed to be invalid, continue with next
   276  				continue
   277  			}
   278  			return nil, fmt.Errorf("abort on block #%d (%x): %s", blocks[res.Index].Number(), blocks[res.Index].Hash(), res.Error)
   279  		}
   280  		if b.BlockHeader == nil {
   281  			return nil, fmt.Errorf("Block insertion should have failed")
   282  		}
   283  
   284  		// validate RLP decoding by checking all values against test file JSON
   285  		if err = validateHeader(b.BlockHeader, cb.Header()); err != nil {
   286  			return nil, fmt.Errorf("Deserialised block header validation failed: %v", err)
   287  		}
   288  		validBlocks = append(validBlocks, b)
   289  	}
   290  	return validBlocks, nil
   291  }
   292  
   293  func validateHeader(h *btHeader, h2 *types.Header) error {
   294  	expectedBloom := mustConvertBytes(h.Bloom)
   295  	if !bytes.Equal(expectedBloom, h2.Bloom.Bytes()) {
   296  		return fmt.Errorf("Bloom: want: %x have: %x", expectedBloom, h2.Bloom.Bytes())
   297  	}
   298  
   299  	expectedCoinbase := mustConvertBytes(h.Coinbase)
   300  	if !bytes.Equal(expectedCoinbase, h2.Coinbase.Bytes()) {
   301  		return fmt.Errorf("Coinbase: want: %x have: %x", expectedCoinbase, h2.Coinbase.Bytes())
   302  	}
   303  
   304  	expectedMixHashBytes := mustConvertBytes(h.MixHash)
   305  	if !bytes.Equal(expectedMixHashBytes, h2.MixDigest.Bytes()) {
   306  		return fmt.Errorf("MixHash: want: %x have: %x", expectedMixHashBytes, h2.MixDigest.Bytes())
   307  	}
   308  
   309  	expectedNonce := mustConvertBytes(h.Nonce)
   310  	if !bytes.Equal(expectedNonce, h2.Nonce[:]) {
   311  		return fmt.Errorf("Nonce: want: %x have: %x", expectedNonce, h2.Nonce)
   312  	}
   313  
   314  	expectedNumber := mustConvertBigInt(h.Number, 16)
   315  	if expectedNumber.Cmp(h2.Number) != 0 {
   316  		return fmt.Errorf("Number: want: %v have: %v", expectedNumber, h2.Number)
   317  	}
   318  
   319  	expectedParentHash := mustConvertBytes(h.ParentHash)
   320  	if !bytes.Equal(expectedParentHash, h2.ParentHash.Bytes()) {
   321  		return fmt.Errorf("Parent hash: want: %x have: %x", expectedParentHash, h2.ParentHash.Bytes())
   322  	}
   323  
   324  	expectedReceiptHash := mustConvertBytes(h.ReceiptTrie)
   325  	if !bytes.Equal(expectedReceiptHash, h2.ReceiptHash.Bytes()) {
   326  		return fmt.Errorf("Receipt hash: want: %x have: %x", expectedReceiptHash, h2.ReceiptHash.Bytes())
   327  	}
   328  
   329  	expectedTxHash := mustConvertBytes(h.TransactionsTrie)
   330  	if !bytes.Equal(expectedTxHash, h2.TxHash.Bytes()) {
   331  		return fmt.Errorf("Tx hash: want: %x have: %x", expectedTxHash, h2.TxHash.Bytes())
   332  	}
   333  
   334  	expectedStateHash := mustConvertBytes(h.StateRoot)
   335  	if !bytes.Equal(expectedStateHash, h2.Root.Bytes()) {
   336  		return fmt.Errorf("State hash: want: %x have: %x", expectedStateHash, h2.Root.Bytes())
   337  	}
   338  
   339  	expectedUncleHash := mustConvertBytes(h.UncleHash)
   340  	if !bytes.Equal(expectedUncleHash, h2.UncleHash.Bytes()) {
   341  		return fmt.Errorf("Uncle hash: want: %x have: %x", expectedUncleHash, h2.UncleHash.Bytes())
   342  	}
   343  
   344  	expectedExtraData := mustConvertBytes(h.ExtraData)
   345  	if !bytes.Equal(expectedExtraData, h2.Extra) {
   346  		return fmt.Errorf("Extra data: want: %x have: %x", expectedExtraData, h2.Extra)
   347  	}
   348  
   349  	expectedDifficulty := mustConvertBigInt(h.Difficulty, 16)
   350  	if expectedDifficulty.Cmp(h2.Difficulty) != 0 {
   351  		return fmt.Errorf("Difficulty: want: %v have: %v", expectedDifficulty, h2.Difficulty)
   352  	}
   353  
   354  	expectedGasLimit := mustConvertBigInt(h.GasLimit, 16)
   355  	if expectedGasLimit.Cmp(h2.GasLimit) != 0 {
   356  		return fmt.Errorf("GasLimit: want: %v have: %v", expectedGasLimit, h2.GasLimit)
   357  	}
   358  	expectedGasUsed := mustConvertBigInt(h.GasUsed, 16)
   359  	if expectedGasUsed.Cmp(h2.GasUsed) != 0 {
   360  		return fmt.Errorf("GasUsed: want: %v have: %v", expectedGasUsed, h2.GasUsed)
   361  	}
   362  
   363  	expectedTimestamp := mustConvertBigInt(h.Timestamp, 16)
   364  	if expectedTimestamp.Cmp(h2.Time) != 0 {
   365  		return fmt.Errorf("Timestamp: want: %v have: %v", expectedTimestamp, h2.Time)
   366  	}
   367  
   368  	return nil
   369  }
   370  
   371  func (t *BlockTest) ValidatePostState(statedb *state.StateDB) error {
   372  	// validate post state accounts in test file against what we have in state db
   373  	for addrString, acct := range t.postAccounts {
   374  		// XXX: is is worth it checking for errors here?
   375  		addr, err := hex.DecodeString(addrString)
   376  		if err != nil {
   377  			return err
   378  		}
   379  		code, err := hex.DecodeString(strings.TrimPrefix(acct.Code, "0x"))
   380  		if err != nil {
   381  			return err
   382  		}
   383  		balance, ok := new(big.Int).SetString(acct.Balance, 0)
   384  		if !ok {
   385  			return err
   386  		}
   387  		nonce, err := strconv.ParseUint(prepInt(16, acct.Nonce), 16, 64)
   388  		if err != nil {
   389  			return err
   390  		}
   391  
   392  		// address is indirectly verified by the other fields, as it's the db key
   393  		code2 := statedb.GetCode(common.BytesToAddress(addr))
   394  		balance2 := statedb.GetBalance(common.BytesToAddress(addr))
   395  		nonce2 := statedb.GetNonce(common.BytesToAddress(addr))
   396  		if !bytes.Equal(code2, code) {
   397  			return fmt.Errorf("account code mismatch for addr: %s want: %s have: %s", addrString, hex.EncodeToString(code), hex.EncodeToString(code2))
   398  		}
   399  		if balance2.Cmp(balance) != 0 {
   400  			return fmt.Errorf("account balance mismatch for addr: %s, want: %d, have: %d", addrString, balance, balance2)
   401  		}
   402  		if nonce2 != nonce {
   403  			return fmt.Errorf("account nonce mismatch for addr: %s want: %d have: %d", addrString, nonce, nonce2)
   404  		}
   405  	}
   406  	return nil
   407  }
   408  
   409  func (test *BlockTest) ValidateImportedHeaders(cm *core.BlockChain, validBlocks []btBlock) error {
   410  	// to get constant lookup when verifying block headers by hash (some tests have many blocks)
   411  	bmap := make(map[string]btBlock, len(test.Json.Blocks))
   412  	for _, b := range validBlocks {
   413  		bmap[b.BlockHeader.Hash] = b
   414  	}
   415  
   416  	// iterate over blocks backwards from HEAD and validate imported
   417  	// headers vs test file. some tests have reorgs, and we import
   418  	// block-by-block, so we can only validate imported headers after
   419  	// all blocks have been processed by ChainManager, as they may not
   420  	// be part of the longest chain until last block is imported.
   421  	for b := cm.CurrentBlock(); b != nil && b.NumberU64() != 0; b = cm.GetBlock(b.Header().ParentHash) {
   422  		bHash := common.Bytes2Hex(b.Hash().Bytes()) // hex without 0x prefix
   423  		if err := validateHeader(bmap[bHash].BlockHeader, b.Header()); err != nil {
   424  			return fmt.Errorf("Imported block header validation failed: %v", err)
   425  		}
   426  	}
   427  	return nil
   428  }
   429  
   430  func convertBlockTests(in map[string]*btJSON) (map[string]*BlockTest, error) {
   431  	out := make(map[string]*BlockTest)
   432  	for name, test := range in {
   433  		var err error
   434  		if out[name], err = convertBlockTest(test); err != nil {
   435  			return out, fmt.Errorf("bad test %q: %v", name, err)
   436  		}
   437  	}
   438  	return out, nil
   439  }
   440  
   441  func convertBlockTest(in *btJSON) (out *BlockTest, err error) {
   442  	// the conversion handles errors by catching panics.
   443  	// you might consider this ugly, but the alternative (passing errors)
   444  	// would be much harder to read.
   445  	defer func() {
   446  		if recovered := recover(); recovered != nil {
   447  			buf := make([]byte, 64<<10)
   448  			buf = buf[:runtime.Stack(buf, false)]
   449  			err = fmt.Errorf("%v\n%s", recovered, buf)
   450  		}
   451  	}()
   452  	out = &BlockTest{preAccounts: in.Pre, postAccounts: in.PostState, Json: in, lastblockhash: in.Lastblockhash}
   453  	out.Genesis = mustConvertGenesis(in.GenesisBlockHeader)
   454  	return out, err
   455  }
   456  
   457  func mustConvertGenesis(testGenesis btHeader) *types.Block {
   458  	hdr := mustConvertHeader(testGenesis)
   459  	hdr.Number = big.NewInt(0)
   460  
   461  	return types.NewBlockWithHeader(hdr)
   462  }
   463  
   464  func mustConvertHeader(in btHeader) *types.Header {
   465  	// hex decode these fields
   466  	header := &types.Header{
   467  		//SeedHash:    mustConvertBytes(in.SeedHash),
   468  		MixDigest:   mustConvertHash(in.MixHash),
   469  		Bloom:       mustConvertBloom(in.Bloom),
   470  		ReceiptHash: mustConvertHash(in.ReceiptTrie),
   471  		TxHash:      mustConvertHash(in.TransactionsTrie),
   472  		Root:        mustConvertHash(in.StateRoot),
   473  		Coinbase:    mustConvertAddress(in.Coinbase),
   474  		UncleHash:   mustConvertHash(in.UncleHash),
   475  		ParentHash:  mustConvertHash(in.ParentHash),
   476  		Extra:       mustConvertBytes(in.ExtraData),
   477  		GasUsed:     mustConvertBigInt(in.GasUsed, 16),
   478  		GasLimit:    mustConvertBigInt(in.GasLimit, 16),
   479  		Difficulty:  mustConvertBigInt(in.Difficulty, 16),
   480  		Time:        mustConvertBigInt(in.Timestamp, 16),
   481  		Nonce:       types.EncodeNonce(mustConvertUint(in.Nonce, 16)),
   482  	}
   483  	return header
   484  }
   485  
   486  func mustConvertBlock(testBlock btBlock) (*types.Block, error) {
   487  	var b types.Block
   488  	r := bytes.NewReader(mustConvertBytes(testBlock.Rlp))
   489  	err := rlp.Decode(r, &b)
   490  	return &b, err
   491  }
   492  
   493  func mustConvertBytes(in string) []byte {
   494  	if in == "0x" {
   495  		return []byte{}
   496  	}
   497  	h := unfuckFuckedHex(strings.TrimPrefix(in, "0x"))
   498  	out, err := hex.DecodeString(h)
   499  	if err != nil {
   500  		panic(fmt.Errorf("invalid hex: %q", h))
   501  	}
   502  	return out
   503  }
   504  
   505  func mustConvertHash(in string) common.Hash {
   506  	out, err := hex.DecodeString(strings.TrimPrefix(in, "0x"))
   507  	if err != nil {
   508  		panic(fmt.Errorf("invalid hex: %q", in))
   509  	}
   510  	return common.BytesToHash(out)
   511  }
   512  
   513  func mustConvertAddress(in string) common.Address {
   514  	out, err := hex.DecodeString(strings.TrimPrefix(in, "0x"))
   515  	if err != nil {
   516  		panic(fmt.Errorf("invalid hex: %q", in))
   517  	}
   518  	return common.BytesToAddress(out)
   519  }
   520  
   521  func mustConvertBloom(in string) types.Bloom {
   522  	out, err := hex.DecodeString(strings.TrimPrefix(in, "0x"))
   523  	if err != nil {
   524  		panic(fmt.Errorf("invalid hex: %q", in))
   525  	}
   526  	return types.BytesToBloom(out)
   527  }
   528  
   529  func mustConvertBigInt(in string, base int) *big.Int {
   530  	in = prepInt(base, in)
   531  	out, ok := new(big.Int).SetString(in, base)
   532  	if !ok {
   533  		panic(fmt.Errorf("invalid integer: %q", in))
   534  	}
   535  	return out
   536  }
   537  
   538  func mustConvertUint(in string, base int) uint64 {
   539  	in = prepInt(base, in)
   540  	out, err := strconv.ParseUint(in, base, 64)
   541  	if err != nil {
   542  		panic(fmt.Errorf("invalid integer: %q", in))
   543  	}
   544  	return out
   545  }
   546  
   547  func LoadBlockTests(file string) (map[string]*BlockTest, error) {
   548  	btjs := make(map[string]*btJSON)
   549  	if err := readJsonFile(file, &btjs); err != nil {
   550  		return nil, err
   551  	}
   552  
   553  	return convertBlockTests(btjs)
   554  }
   555  
   556  // Nothing to see here, please move along...
   557  func prepInt(base int, s string) string {
   558  	if base == 16 {
   559  		if strings.HasPrefix(s, "0x") {
   560  			s = s[2:]
   561  		}
   562  		if len(s) == 0 {
   563  			s = "00"
   564  		}
   565  		s = nibbleFix(s)
   566  	}
   567  	return s
   568  }
   569  
   570  // don't ask
   571  func unfuckFuckedHex(almostHex string) string {
   572  	return nibbleFix(strings.Replace(almostHex, "v", "", -1))
   573  }
   574  
   575  func nibbleFix(s string) string {
   576  	if len(s)%2 != 0 {
   577  		s = "0" + s
   578  	}
   579  	return s
   580  }