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