// buildExplorerBlock takes a block and its height and uses it to construct an // explorer block. func (srv *Server) buildExplorerBlock(height types.BlockHeight, block types.Block) ExplorerBlock { var mpoids []types.SiacoinOutputID for i := range block.MinerPayouts { mpoids = append(mpoids, block.MinerPayoutID(uint64(i))) } var etxns []ExplorerTransaction for _, txn := range block.Transactions { etxns = append(etxns, srv.buildExplorerTransaction(height, block.ID(), txn)) } facts, exists := srv.explorer.BlockFacts(height) if build.DEBUG && !exists { panic("incorrect request to buildExplorerBlock - block does not exist") } return ExplorerBlock{ MinerPayoutIDs: mpoids, Transactions: etxns, RawBlock: block, BlockFacts: facts, } }
// New returns a new State, containing at least the genesis block. If there is // an existing block database present in saveDir, it will be loaded. Otherwise, // a new database will be created. func New(gateway modules.Gateway, saveDir string) (*State, error) { if gateway == nil { return nil, ErrNilGateway } // Create the State object. cs := &State{ blockMap: make(map[types.BlockID]*blockNode), dosBlocks: make(map[types.BlockID]struct{}), currentPath: make([]types.BlockID, 1), siacoinOutputs: make(map[types.SiacoinOutputID]types.SiacoinOutput), fileContracts: make(map[types.FileContractID]types.FileContract), siafundOutputs: make(map[types.SiafundOutputID]types.SiafundOutput), delayedSiacoinOutputs: make(map[types.BlockHeight]map[types.SiacoinOutputID]types.SiacoinOutput), gateway: gateway, mu: sync.New(modules.SafeMutexDelay, 1), } // Create the genesis block and add it as the BlockRoot. genesisBlock := types.Block{ Timestamp: types.GenesisTimestamp, Transactions: []types.Transaction{ {SiafundOutputs: types.GenesisSiafundAllocation}, }, } cs.blockRoot = &blockNode{ block: genesisBlock, childTarget: types.RootTarget, depth: types.RootDepth, diffsGenerated: true, } cs.blockMap[genesisBlock.ID()] = cs.blockRoot // Fill out the consensus information for the genesis block. cs.currentPath[0] = genesisBlock.ID() cs.siacoinOutputs[genesisBlock.MinerPayoutID(0)] = types.SiacoinOutput{ Value: types.CalculateCoinbase(0), UnlockHash: types.ZeroUnlockHash, } // Allocate the Siafund addresses by putting them all in a big transaction // and applying the diffs. for i, siafundOutput := range genesisBlock.Transactions[0].SiafundOutputs { sfid := genesisBlock.Transactions[0].SiafundOutputID(i) sfod := modules.SiafundOutputDiff{ Direction: modules.DiffApply, ID: sfid, SiafundOutput: siafundOutput, } cs.commitSiafundOutputDiff(sfod, modules.DiffApply) cs.blockRoot.siafundOutputDiffs = append(cs.blockRoot.siafundOutputDiffs, sfod) } // Send out genesis block update. cs.updateSubscribers(nil, []*blockNode{cs.blockRoot}) // Create the consensus directory. err := os.MkdirAll(saveDir, 0700) if err != nil { return nil, err } // During short tests, use an in-memory database. if build.Release == "testing" && testing.Short() { cs.db = persist.NilDB } else { // Otherwise, try to load an existing database from disk. err = cs.load(saveDir) if err != nil { return nil, err } } // Register RPCs gateway.RegisterRPC("SendBlocks", cs.sendBlocks) gateway.RegisterRPC("RelayBlock", cs.RelayBlock) gateway.RegisterConnectCall("SendBlocks", cs.receiveBlocks) // Spawn resynchronize loop. go cs.threadedResynchronize() return cs, nil }
// addBlockDB parses a block and adds it to the database func (e *Explorer) addBlockDB(b types.Block) error { // Special case for the genesis block, which does not have a // valid parent, and for testing, as tests will not always use // blocks in consensus var blocktarget types.Target if b.ID() == e.genesisBlockID { blocktarget = types.RootDepth e.blockchainHeight = 0 } else { var exists bool blocktarget, exists = e.cs.ChildTarget(b.ParentID) if build.DEBUG { if build.Release == "testing" { blocktarget = types.RootDepth } else if !exists { panic("Applied block not in consensus") } } } // Check if the block exsts. var exists bool dbErr := e.db.View(func(tx *bolt.Tx) error { id := b.ID() block := tx.Bucket([]byte("Blocks")).Get(id[:]) exists = block != nil return nil }) if dbErr != nil { return dbErr } if exists { return nil } tx, err := newBoltTx(e.db) if err != nil { return err } defer tx.Rollback() // Construct the struct that will be inside the heights map blockStruct := blockData{ Block: b, Height: e.blockchainHeight, } tx.addNewHash("Blocks", hashBlock, crypto.Hash(b.ID()), blockStruct) bSum := modules.ExplorerBlockData{ ID: b.ID(), Timestamp: b.Timestamp, Target: blocktarget, Size: uint64(len(encoding.Marshal(b))), } tx.putObject("Heights", e.blockchainHeight, bSum) tx.putObject("Hashes", crypto.Hash(b.ID()), hashBlock) // Insert the miner payouts as new outputs for i, payout := range b.MinerPayouts { tx.addAddress(payout.UnlockHash, types.TransactionID(b.ID())) tx.addNewOutput(b.MinerPayoutID(uint64(i)), types.TransactionID(b.ID())) } // Insert each transaction for i, txn := range b.Transactions { tx.addNewHash("Transactions", hashTransaction, crypto.Hash(txn.ID()), txInfo{b.ID(), i}) tx.addTransaction(txn) } return tx.commit() }
// Add a couple blocks to the database, then perform lookups to see if // they were added and crossed referenced correctly func (et *explorerTester) testAddBlock(t *testing.T) error { // This block will *NOT* be valid, but should contain // addresses that can cross reference each other. b1 := types.Block{ ParentID: types.BlockID(genHashNum(1)), Nonce: [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, Timestamp: 3, MinerPayouts: []types.SiacoinOutput{types.SiacoinOutput{ Value: types.NewCurrency64(4), UnlockHash: types.UnlockHash(genHashNum(5)), }}, Transactions: nil, } // This should not error at least... lockID := et.explorer.mu.Lock() err := et.explorer.addBlockDB(b1) et.explorer.mu.Unlock(lockID) if err != nil { return errors.New("Error inserting basic block: " + err.Error()) } // Again, not a valid block at all. b2 := types.Block{ ParentID: b1.ID(), Nonce: [8]byte{7, 7, 7, 7, 7, 7, 7, 7}, Timestamp: 8, MinerPayouts: nil, Transactions: []types.Transaction{types.Transaction{ SiacoinInputs: []types.SiacoinInput{types.SiacoinInput{ ParentID: b1.MinerPayoutID(0), }}, FileContracts: []types.FileContract{types.FileContract{ UnlockHash: types.UnlockHash(genHashNum(10)), }}, }}, } lockID = et.explorer.mu.Lock() err = et.explorer.addBlockDB(b2) et.explorer.mu.Unlock(lockID) if err != nil { return errors.New("Error inserting block 2: " + err.Error()) } // Now query the database to see if it has been linked properly lockID = et.explorer.mu.RLock() bytes, err := et.explorer.db.GetFromBucket("Blocks", encoding.Marshal(b1.ID())) et.explorer.mu.RUnlock(lockID) var b types.Block err = encoding.Unmarshal(bytes, &b) if err != nil { return errors.New("Could not decode loaded block") } if b.ID() != b1.ID() { return errors.New("Block 1 not stored properly") } // Query to see if the input is added to the output field lockID = et.explorer.mu.RLock() bytes, err = et.explorer.db.GetFromBucket("SiacoinOutputs", encoding.Marshal(b1.MinerPayoutID(0))) et.explorer.mu.RUnlock(lockID) if err != nil { t.Fatal(err.Error()) } if bytes == nil { return errors.New("Output is nil") } var ot outputTransactions err = encoding.Unmarshal(bytes, &ot) if err != nil { return errors.New("Could not decode loaded block") } if ot.InputTx == (types.TransactionID{}) { return errors.New("Input not added as output") } return nil }